Compare commits

..

2 Commits

5 changed files with 81 additions and 384 deletions

7
README
View File

@ -10,10 +10,3 @@ double precision shaders.
To build and run: To build and run:
./build.sh && ./gpu-frac ./build.sh && ./gpu-frac
Since both GLSL and C use CPP directives, I parameterised the program
a bunch and stuck a load of cool presets in config.h, which can be
simply be uncommented and commented as is required. All the #ifdefs
make the code a bit harder to read but it's nice being able to twiddle
numbers and change the visualisation without messing around editing
and commenting out code.

View File

@ -2,4 +2,4 @@
set -e set -e
glslc -fshader-stage=vert vert_shader.glsl -o vert_shader.spv glslc -fshader-stage=vert vert_shader.glsl -o vert_shader.spv
glslc -fshader-stage=frag frag_shader.glsl -o frag_shader.spv glslc -fshader-stage=frag frag_shader.glsl -o frag_shader.spv
clang -std=c11 -pedantic -Wall -Wextra -lglfw -lm -lvulkan main.c -o gpu-fractal clang -std=c11 -pedantic -Wall -Wextra -lglfw -lvulkan main.c -o gpu-fractal

View File

@ -1,77 +0,0 @@
/*
* Copyright (c) Camden Dixie O'Brien
* SPDX-License-Identifier: AGPL-3.0-only
*/
#define MAXITER 1000
// Pretty Julia set zoom
//#define SS 9
//#define JULIA
//#define JULIA_C_RE 0.285
//#define JULIA_C_IM 0.010
//#define CENTRE_RE -0.47353731
//#define CENTRE_IM -0.18894516
//#define ZOOMRATE 0.01
// Cool zoomy swirly animated Julia set
//#define SS 9
//#define JULIA
//#define JULIA_C_RE 0.300
//#define JULIA_DTHETA 0.00001
//#define ZOOMRATE 0.0004
// Nice Mandelbrot zoom
//#define SS 9
//#define CENTRE_RE -0.743643887
//#define CENTRE_IM 0.131825904
//#define ZOOMRATE 0.004
// Cool warping Mandelbrot zoom
//#define SS 4
//#define CENTRE_RE -0.900000000
//#define POWRATE 0.0001
//#define ZOOMRATE 0.001
// Funky swirly, warping Julia zoom
//#define SS 9
//#define JULIA
//#define JULIA_C_RE 0.5
//#define JULIA_C_IM 0.5
//#define CENTRE_RE 0.5
//#define CENTRE_IM -0.5
//#define JULIA_DTHETA -0.00002
//#define POWRATE 0.0001
//#define ZOOMRATE 0.0003
// Cool tendril stuff (complex power)
//#define SS 4
//#define CPOW_DTHETA 0.000
//#define MAXMAG 100.0
//#define CENTRE_RE -0.84880415
//#define CENTRE_IM 0.03921157
//#define CPOW_START_RE 1.96550429
//#define CPOW_START_IM 0.36985874
//#define ZOOMRATE 0.01
// ??
//#define SS 4
//#define THING
//#define JULIA_C_RE -0.9999999506519784
//#define JULIA_C_IM 3.141592601914583e-4
//#define JULIA_DTHETA 0.00001
//#define MAXMAG 100.0
//#define STARTSCALE 4
//#define ZOOMRATE 0.01
// Funky complex power julia set
#define SS 4
#define JULIA
#define JULIA_C_RE -0.010
#define JULIA_C_IM 0.285
#define JULIA_DTHETA 0.0001
#define CPOW_START_RE 0.0
#define CPOW_START_IM 2.0
#define CPOW_DTHETA 0.0001
#define MAXMAG 100.0
#define STARTSCALE 1

View File

@ -4,142 +4,54 @@
*/ */
#version 450 #version 450
#extension GL_ARB_gpu_shader_fp64 : enable
#include "config.h" #define MAXITER 1000
#define NCOLS 8
#if !defined(CPOW_DTHETA) && !defined(THING)
#define MAXMAG 2.0
#endif
layout(location = 0) out vec4 out_colour; layout(location = 0) out vec4 out_colour;
layout(push_constant) uniform Constants { layout(push_constant) uniform Constants {
vec2 img_size; dvec2 img_size;
#if defined(JULIA) || defined(THING) dvec2 shift;
vec2 julia; double zoom;
#endif
vec2 centre;
#ifdef CPOW_DTHETA
vec2 cpow;
#endif
#ifdef POWRATE
float zpow;
#endif
float scale;
} params; } params;
vec3 incol = vec3(0.000000, 0.014444, 0.027321); vec2 ss_offsets[4] = vec2[](
vec3 palette[NCOLS] = vec3[](
vec3(0.000000, 0.049707, 0.107023),
vec3(0.025187, 0.064803, 0.177888),
vec3(0.254152, 0.080220, 0.274677),
vec3(0.502886, 0.080220, 0.278894),
vec3(1.000000, 0.124772, 0.119538),
vec3(1.000000, 0.234551, 0.030713),
vec3(1.000000, 0.381326, 0.000000),
vec3(1.000000, 0.651406, 0.215861)
);
#if SS == 4
vec2 ss_offsets[SS] = vec2[](
vec2(-0.25, -0.25), vec2(-0.25, -0.25),
vec2( 0.25, -0.25), vec2( 0.25, -0.25),
vec2(-0.25, 0.25), vec2(-0.25, 0.25),
vec2( 0.25, 0.25) vec2( 0.25, 0.25)
); );
#elif SS == 9
vec2 ss_offsets[SS] = vec2[](
vec2(-0.33, -0.33), vec2(0.0, -0.33), vec2(0.33, -0.33),
vec2(-0.33, 0.0), vec2(0.0, 0.0), vec2(0.33, 0.0),
vec2(-0.33, 0.33), vec2(0.0, 0.33), vec2(0.33, 0.33)
);
#else
#error "Unsupported supersampling count"
#endif
vec2 maptoz(vec2 xy) dvec2 maptoz(vec2 xy)
{ {
float aspect = params.img_size.x / params.img_size.y; dvec2 dxy = dvec2(xy);
vec2 z = 4.0 * (xy / params.img_size.xy - 0.5); dvec2 uv = ((dxy / params.img_size.xy) * 4.0LF - 2.0LF);
z.x *= aspect; return params.zoom * uv + params.shift;
return params.scale * z + params.centre;
} }
vec2 cmul(vec2 a, vec2 b) double mandelbrot(dvec2 c) {
{ dvec2 z = dvec2(0.0LF, 0.0LF);
return vec2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
}
vec2 cexp(vec2 z)
{
return exp(z.x) * vec2(cos(z.y), sin(z.y));
}
vec2 cln(vec2 z)
{
return vec2(log(length(z)), atan(z.y, z.x));
}
vec2 cpow(vec2 z, vec2 p)
{
if (z.x == 0.0 && z.y == 0.0) return vec2(0.0, 0.0);
return cexp(cmul(p, cln(z)));
}
vec2 zpow(vec2 z, float p)
{
float mag = pow(length(z), p);
float arg = p * atan(z.y, z.x);
return mag * vec2(cos(arg), sin(arg));
}
int fractal(vec2 c) {
#ifdef JULIA
vec2 z = c;
c = params.julia;
#else
vec2 z = vec2(0.0, 0.0);
#endif
for (int i = 0; i < MAXITER; ++i) { for (int i = 0; i < MAXITER; ++i) {
#ifdef CPOW_DTHETA z = dvec2(z.x * z.x - z.y * z.y, 2.0LF * z.x * z.y) + c;
z = cpow(z, params.cpow) + c; if (length(z) > 2.0LF)
#elif defined(POWRATE) return double(i) / double(MAXITER);
z = zpow(z, params.zpow) + c;
#elif defined(THING)
z = cpow(z, c) + params.julia;
#else
z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;
#endif
if (length(z) > MAXMAG)
return i;
} }
return -1; return 0.0;
} }
vec3 colour(int i) { vec3 colour(double i) {
if (i == -1) { float fi = float(i);
return incol; return vec3(fi, fi * fi, fi * fi * fi);
} else {
float c = float(NCOLS) * sqrt(float(i) / float(MAXITER));
int fc = int(floor(c));
int cc = int(ceil(c));
float p = c - float(fc);
p = smoothstep(0.0, 1.0, p);
return mix(palette[fc], palette[cc], p);
}
} }
void main() void main()
{ {
vec2 c; dvec2 c;
vec3 col = vec3(0.0, 0.0, 0.0); vec3 col = vec3(0.0, 0.0, 0.0);
for (int i = 0; i < SS; ++i) { for (int i = 0; i < 4; ++i) {
c = maptoz(gl_FragCoord.xy + ss_offsets[i]); c = maptoz(gl_FragCoord.xy + ss_offsets[i]);
col += colour(fractal(c)); col += colour(mandelbrot(c));
} }
col /= SS;
out_colour = vec4(col, 1.0); out_colour = vec4(col, 1.0);
} }

247
main.c
View File

@ -5,12 +5,8 @@
#define GLFW_INCLUDE_VULKAN #define GLFW_INCLUDE_VULKAN
#include "config.h"
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <assert.h> #include <assert.h>
#include <complex.h>
#include <math.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@ -19,130 +15,25 @@
#define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b))
#define CLAMP(x, min, max) MAX((min), MIN(x, (max))) #define CLAMP(x, min, max) MAX((min), MIN(x, (max)))
#define PHYSICAL_DEV_BUFFER_SIZE 8
#define QUEUE_FAMILY_PROP_BUFFER_SIZE 16 #define QUEUE_FAMILY_PROP_BUFFER_SIZE 16
#define EXT_PROP_BUFFER_SIZE 256 #define EXT_PROP_BUFFER_SIZE 256
#define SURFACE_FMT_BUFFER_SIZE 64 #define SURFACE_FMT_BUFFER_SIZE 64
#define PRESENT_MODE_BUFFER_SIZE 8 #define PRESENT_MODE_BUFFER_SIZE 8
#define SWAPCHAIN_IMG_BUFFER_SIZE 4 #define SWAPCHAIN_IMG_BUFFER_SIZE 4
#define MAX_SHADER_SIZE (8 * 1024) #define MAX_SHADER_SIZE (4 * 1024)
#define FASTFORWARD_RATE 100
#if defined(CPOW_DTHETA) && defined(POWRATE)
#error "Cannot define CPOW_DTHETA and POWRATE simulataneously"
#endif
#ifndef STARTSCALE
#define STARTSCALE 1.0
#endif
typedef struct { typedef struct {
float width, height; double width, height;
#if defined(JULIA) || defined(THING)
struct { struct {
float re, im; double x, y;
} julia; } shift;
#endif double zoom;
struct {
float re, im;
} centre;
#ifdef CPOW_DTHETA
struct {
float re, im;
} cpow;
#endif
#ifdef POWRATE
float zpow;
#endif
float scale;
} params_t; } params_t;
static const char *layers[] = { "VK_LAYER_KHRONOS_validation" }; static const char *layers[] = { "VK_LAYER_KHRONOS_validation" };
static const char *extensions[] = { static const char *swapchain_ext_name = VK_KHR_SWAPCHAIN_EXTENSION_NAME;
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
};
#define NEXTENSIONS (sizeof(extensions) / sizeof(extensions[0]))
// Initial parameters
static params_t params = {
#if defined(JULIA) || defined(THING)
.julia = {
#ifdef JULIA_C_RE
.re = JULIA_C_RE,
#endif
#ifdef JULIA_C_IM
.im = JULIA_C_IM,
#endif
},
#endif
#if defined(CENTRE_RE) || defined(CENTRE_IM)
.centre = {
#ifdef CENTRE_RE
.re = CENTRE_RE,
#endif
#ifdef CENTRE_IM
.im = CENTRE_IM,
#endif
},
#endif
#ifdef CPOW_DTHETA
.cpow = { .re = CPOW_START_RE, .im = CPOW_START_IM },
#endif
#ifdef POWRATE
.zpow = 2.0,
#endif
.scale = STARTSCALE,
};
static bool paused = false, fastforward = false;
static void
mouse_button_callback(GLFWwindow *window, int button, int action, int mods)
{
(void)window;
(void)button;
(void)mods;
if (action == GLFW_RELEASE)
return;
double x, y;
glfwGetCursorPos(window, &x, &y);
double zx = 4.0 * (x / params.width - 0.5);
double zy = 4.0 * (y / params.height - 0.5);
zx *= params.width / params.height;
zx = params.scale * zx + params.centre.re;
zy = params.scale * zy + params.centre.im;
printf("CLICKED: %.8f + %.8fi\n", zx, zy);
params.centre.re = zx;
params.centre.im = zy;
}
static void
key_callback(GLFWwindow *window, int key, int scancode, int action, int mods)
{
(void)window;
(void)scancode;
(void)mods;
switch (key) {
case GLFW_KEY_SPACE:
if (action == GLFW_PRESS)
paused = !paused;
break;
case GLFW_KEY_F:
if (action == GLFW_PRESS)
fastforward = true;
else if (action == GLFW_RELEASE)
fastforward = false;
break;
}
}
static VkShaderModule load_shader_module(VkDevice dev, const char *path) static VkShaderModule load_shader_module(VkDevice dev, const char *path)
{ {
@ -168,24 +59,18 @@ static VkShaderModule load_shader_module(VkDevice dev, const char *path)
int main(void) int main(void)
{ {
// Set up window // Set up window and surface
glfwInit(); glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
GLFWmonitor *monitor = glfwGetPrimaryMonitor(); GLFWwindow *window
const GLFWvidmode *mode = glfwGetVideoMode(monitor); = glfwCreateWindow(900, 900, "GPU Fractal", NULL, NULL);
GLFWwindow *window = glfwCreateWindow(
mode->width, mode->height, "GPU Fractal", monitor, NULL);
assert(window); assert(window);
// Register input callbacks
glfwSetMouseButtonCallback(window, mouse_button_callback);
glfwSetKeyCallback(window, key_callback);
// Initialise Vulkan instance. // Initialise Vulkan instance.
const VkApplicationInfo app_info = { const VkApplicationInfo app_info = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = "GPU Fractal", .pApplicationName = "Vulkan Test",
.applicationVersion = VK_MAKE_VERSION(1, 0, 0), .applicationVersion = VK_MAKE_VERSION(1, 0, 0),
.pEngineName = "No Engine", .pEngineName = "No Engine",
.engineVersion = VK_MAKE_VERSION(1, 0, 0), .engineVersion = VK_MAKE_VERSION(1, 0, 0),
@ -212,10 +97,22 @@ int main(void)
assert(result == VK_SUCCESS); assert(result == VK_SUCCESS);
// Select a physical device // Select a physical device
VkPhysicalDevice physical_devs[PHYSICAL_DEV_BUFFER_SIZE];
uint32_t dev_count = PHYSICAL_DEV_BUFFER_SIZE;
result = vkEnumeratePhysicalDevices(inst, &dev_count, physical_devs);
assert(result == VK_SUCCESS);
bool found_physical_dev = false;
VkPhysicalDevice physical_dev; VkPhysicalDevice physical_dev;
uint32_t dev_count = 1; for (unsigned i = 0; i < dev_count; ++i) {
result = vkEnumeratePhysicalDevices(inst, &dev_count, &physical_dev); // Check that the device supports double precision
assert(result == VK_SUCCESS || result == VK_INCOMPLETE); VkPhysicalDeviceFeatures features;
vkGetPhysicalDeviceFeatures(physical_devs[i], &features);
if (features.shaderFloat64) {
physical_dev = physical_devs[i];
found_physical_dev = true;
}
}
assert(found_physical_dev);
// Select queue family // Select queue family
uint32_t queue_family_count = QUEUE_FAMILY_PROP_BUFFER_SIZE; uint32_t queue_family_count = QUEUE_FAMILY_PROP_BUFFER_SIZE;
@ -239,28 +136,6 @@ int main(void)
} }
assert(found_queue_family); assert(found_queue_family);
// Check that the physical device supports all extensions
uint32_t ext_count = EXT_PROP_BUFFER_SIZE;
VkExtensionProperties ext_props[EXT_PROP_BUFFER_SIZE];
result = vkEnumerateDeviceExtensionProperties(
physical_dev, NULL, &ext_count, ext_props);
assert(result == VK_SUCCESS);
bool supported[NEXTENSIONS] = { 0 };
for (unsigned i = 0; i < ext_count; ++i) {
for (unsigned j = 0; j < NEXTENSIONS; ++j) {
if (strcmp(extensions[j], ext_props[i].extensionName) == 0) {
supported[j] = true;
break;
}
}
}
for (unsigned j = 0; j < NEXTENSIONS; ++j) {
if (!supported[j]) {
fprintf(stderr, "Unsupported: %s\n", extensions[j]);
assert(false);
}
}
// Create the logical device and get the queue handle. // Create the logical device and get the queue handle.
float queue_priority = 1.0; float queue_priority = 1.0;
const VkDeviceQueueCreateInfo queue_create_info = { const VkDeviceQueueCreateInfo queue_create_info = {
@ -269,12 +144,16 @@ int main(void)
.queueCount = 1, .queueCount = 1,
.pQueuePriorities = &queue_priority, .pQueuePriorities = &queue_priority,
}; };
const VkPhysicalDeviceFeatures enabled_features = {
.shaderFloat64 = VK_TRUE,
};
const VkDeviceCreateInfo dev_create_info = { const VkDeviceCreateInfo dev_create_info = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pQueueCreateInfos = &queue_create_info, .pQueueCreateInfos = &queue_create_info,
.queueCreateInfoCount = 1, .queueCreateInfoCount = 1,
.enabledExtensionCount = NEXTENSIONS, .enabledExtensionCount = 1,
.ppEnabledExtensionNames = extensions, .ppEnabledExtensionNames = &swapchain_ext_name,
.pEnabledFeatures = &enabled_features,
}; };
VkDevice dev; VkDevice dev;
result = vkCreateDevice(physical_dev, &dev_create_info, NULL, &dev); result = vkCreateDevice(physical_dev, &dev_create_info, NULL, &dev);
@ -282,6 +161,21 @@ int main(void)
VkQueue queue; VkQueue queue;
vkGetDeviceQueue(dev, queue_family, 0, &queue); vkGetDeviceQueue(dev, queue_family, 0, &queue);
// Check that the physical device has swap chain support
uint32_t ext_count = EXT_PROP_BUFFER_SIZE;
VkExtensionProperties ext_props[EXT_PROP_BUFFER_SIZE];
result = vkEnumerateDeviceExtensionProperties(
physical_dev, NULL, &ext_count, ext_props);
assert(result == VK_SUCCESS);
bool swapchain_support = false;
for (unsigned i = 0; i < ext_count; ++i) {
if (strcmp(swapchain_ext_name, ext_props[i].extensionName) == 0) {
swapchain_support = true;
break;
}
}
assert(swapchain_support);
// Select a surface format, preferring R8G8B8A8_SRGB with // Select a surface format, preferring R8G8B8A8_SRGB with
// SRGB_NONLINEAR colour space. // SRGB_NONLINEAR colour space.
uint32_t surface_fmt_count = SURFACE_FMT_BUFFER_SIZE; uint32_t surface_fmt_count = SURFACE_FMT_BUFFER_SIZE;
@ -584,15 +478,15 @@ int main(void)
result = vkCreateFence(dev, &fence_config, NULL, &in_flight); result = vkCreateFence(dev, &fence_config, NULL, &in_flight);
assert(result == VK_SUCCESS); assert(result == VK_SUCCESS);
// Initialise shader parameters params_t params = {
params.width = (float)swapchain_extent.width; .width = (double)swapchain_extent.width,
params.height = (float)swapchain_extent.height; .height = (double)swapchain_extent.height,
#ifdef JULIA_DTHETA .shift = {
const double complex julia_rotz = cexp(I * JULIA_DTHETA); .x = -0.743643887037158704752191506114774,
#endif .y = 0.131825904205311970493132056385139,
#ifdef CPOW_DTHETA },
const double complex cpow_rotz = cexp(I * CPOW_DTHETA); .zoom = 1.0,
#endif };
while (!glfwWindowShouldClose(window)) { while (!glfwWindowShouldClose(window)) {
glfwPollEvents(); glfwPollEvents();
@ -629,7 +523,6 @@ int main(void)
vkCmdBeginRenderPass( vkCmdBeginRenderPass(
cmd_buf, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE); cmd_buf, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE);
// Send parameters
vkCmdPushConstants( vkCmdPushConstants(
cmd_buf, pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, cmd_buf, pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(params), &params); sizeof(params), &params);
@ -672,32 +565,8 @@ int main(void)
result = vkQueuePresentKHR(queue, &present_info); result = vkQueuePresentKHR(queue, &present_info);
assert(result == VK_SUCCESS); assert(result == VK_SUCCESS);
if (paused) // Increment zoom
continue; params.zoom *= 0.99;
for (int i = 0; i < (fastforward ? FASTFORWARD_RATE : 1); ++i) {
#ifdef JULIA_DTHETA
double complex julia = params.julia.re + I * params.julia.im;
julia *= julia_rotz;
params.julia.re = (float)creal(julia);
params.julia.im = (float)cimag(julia);
#endif
#ifdef ZOOMRATE
params.scale *= (1 - ZOOMRATE);
#endif
#ifdef CPOW_DTHETA
double complex cpow = params.cpow.re + I * params.cpow.im;
cpow *= cpow_rotz;
params.cpow.re = (float)creal(cpow);
params.cpow.im = (float)cimag(cpow);
#endif
#ifdef POWRATE
params.zpow += POWRATE;
#endif
}
} }
vkDeviceWaitIdle(dev); vkDeviceWaitIdle(dev);