Compare commits

...

33 commits

Author SHA1 Message Date
Stephen Seo 2854aa5104 Make OpenCL default backend
Order of backends to use:
OpenCL -> Vulkan -> CPU threads

Unless I figure out a way to make Vulkan faster, OpenCL will be the
default backend used, or at least it will have higher priority than
Vulkan if both OpenCL and Vulkan is available.
2024-04-01 11:19:55 +09:00
Stephen Seo f3d1ad425c Vulkan compute: Minor refactoring 2024-03-27 11:27:43 +09:00
Stephen Seo 7fb458dad5 Vulkan compute: Minor refactoring 2024-03-26 14:00:46 +09:00
Stephen Seo 5913b7669b Vulkan compute: Refactor pbp integer setting loop 2024-03-26 13:57:13 +09:00
Stephen Seo d137dc44ff inline get_filter in vulkan compute impl. 2024-03-26 13:49:37 +09:00
Stephen Seo 045d3ef18d Use "invalidate", not "flush" for buffer read 2024-03-26 13:37:51 +09:00
Stephen Seo 3a97f14199 Use "cached", not "coherent" for staging buffers 2024-03-26 12:21:40 +09:00
Stephen Seo 2d47b7f892 Attempt to optimize vulkan compute 2024-03-25 16:53:42 +09:00
Stephen Seo 47ba03337b Impl. Vulkan compute
Tests indicate Vulkan compute runs 2x slower than OpenCL, so there
probably is room for optimization.
2024-03-25 13:27:06 +09:00
Stephen Seo d4661996d2 WIP Vulkan compute: descriptor set and command buf
Some more Vulkan initialization code to set up Vulkan compute.
2024-03-25 11:39:50 +09:00
Stephen Seo ea66a44238 QueueFamilyIndices struct: fn definitions in .cpp
Instead of defining struct functions in header file, only declare them
in the header file and define them in the source file.
2024-03-22 17:03:56 +09:00
Stephen Seo 16f0e0e865 Define vulkan specific helper fns in header 2024-03-22 16:59:23 +09:00
Stephen Seo 03b3c90e9a Minor fix
Fix debug messenger initialization check.
2024-03-21 12:31:38 +09:00
Stephen Seo 6ed76af210 Restructure vulkan init code
Vulkan init code was resturctured so that "temporary" structs (e.g.
vulkan "info create" structs) are discarded after they are used.
2024-03-21 12:07:46 +09:00
Stephen Seo 3b11564a4a Addendum note to previous commit
It was assumed in the previous commit's message that the shader data was
stored on the stack. In actuality, the usage of std::vector<char> uses
dynamically allocated memory, which means the data should be on the heap
not the stack.
2024-03-21 11:56:12 +09:00
Stephen Seo 020993fb19 Refactor: Move shader loading code to inner scope
This prevents the loaded shader data from persisting on the stack even
when it is no longer needed.
2024-03-20 19:25:22 +09:00
Stephen Seo d29d2434ab utility::Cleanup: Replace "Nop"
A default constructor suffices where "Nop" was used, so it was removed.
2024-03-20 19:18:37 +09:00
Stephen Seo d5d722022c CMakeLists.txt: Debug flag "-Og" instead of "-O0" 2024-03-20 16:20:13 +09:00
Stephen Seo 3b0a5ab7dd WIP Vulkan compute: Fix buffer data
"filter_size" integer must be odd. This is how it is defined for the
OpenCL implementation.
2024-03-20 16:12:23 +09:00
Stephen Seo 5cee471f1f WIP Vulkan compute: create buffers 2024-03-20 16:00:47 +09:00
Stephen Seo 7cca2bfbff WIP vulkan compute: create command pool 2024-03-20 14:29:48 +09:00
Stephen Seo a7dc666082 Refactor: code where neither Vulkan/OpenCL is used 2024-03-20 14:29:03 +09:00
Stephen Seo bc02b924e2 WIP vulkan compute: create compute pipeline, fixes
Fix Cleanup class.

Implement setting up compute pipeline.
2024-03-20 11:54:38 +09:00
Stephen Seo 8c1190d923 Refactor glslc usage 2024-03-20 11:11:22 +09:00
Stephen Seo bd5cfaebd3 Fix minor typo 2024-03-19 16:24:08 +09:00
Stephen Seo 6cefcc5f94 Compile glsl with glslc, fixes
Fix incorrectly set "bindings" for descriptors.

Fix blue_gen.glsl .

Program now attempts to compile blue_gen.glsl using "glslc".
2024-03-19 16:13:10 +09:00
Stephen Seo b73c6fb94b WIP Vulkan compute: compute descriptors, glsl 2024-03-19 14:58:35 +09:00
Stephen Seo a0de033d34 WIP Vulkan compute: initialize VkDevice 2024-03-08 13:48:42 +09:00
Stephen Seo 38f7248f49 Minor refactoring
Use `const std::array<...>` instead of `const std::vector<...>`.
2024-03-07 16:47:37 +09:00
Stephen Seo d0f14585b6 WIP vulkan compute: query physical devices 2024-03-07 12:13:32 +09:00
Stephen Seo aaa986b0eb Remove erronous Vulkan init. property
TODO: Vulkan compute
2024-03-06 17:56:30 +09:00
Stephen Seo c823f90c14 Update LICENSE year 2024-03-06 17:38:53 +09:00
Stephen Seo 4e4ee7f558 Begin work on Vulkan compute (WIP)
Some Vulkan initialization stuff WIP.
2024-03-06 17:04:08 +09:00
10 changed files with 1499 additions and 31 deletions

View file

@ -6,12 +6,13 @@ set(blueNoiseGen_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/image.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/arg_parse.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/utility.cpp
)
add_compile_options(
-Wall -Wextra -Wpedantic
$<$<COMPILE_LANGUAGE:CXX>:-Weffc++>
$<$<CONFIG:DEBUG>:-O0>
$<$<CONFIG:DEBUG>:-Og>
)
if(NOT DEFINED CMAKE_BUILD_TYPE OR NOT CMAKE_BUILD_TYPE)
@ -29,29 +30,48 @@ if(NOT DEFINED DISABLE_OPENCL OR NOT DISABLE_OPENCL)
else()
message(STATUS "Not checking for OpenCL")
endif()
if(NOT DEFINED DISABLE_VULKAN OR NOT DISABLE_VULKAN)
find_package(Vulkan)
if(NOT DEFINED Vulkan_FOUND)
set(DISABLE_VULKAN True)
message(WARNING "Vulkan not found, Vulkan usage is disabled.")
endif()
else()
message(STATUS "Not checking for Vulkan")
endif()
find_package(PNG REQUIRED)
add_executable(blueNoiseGen ${blueNoiseGen_SOURCES})
target_compile_features(blueNoiseGen PUBLIC cxx_std_17)
target_compile_definitions(blueNoiseGen PRIVATE CL_TARGET_OPENCL_VERSION=300)
target_include_directories(blueNoiseGen PUBLIC Threads::Threads ${PNG_INCLUDE_DIRS})
target_link_libraries(blueNoiseGen PUBLIC Threads::Threads ${PNG_LIBRARIES})
if(DEFINED DISABLE_OPENCL AND DISABLE_OPENCL)
message(STATUS "OpenCL usage is disabled.")
target_include_directories(blueNoiseGen PUBLIC
Threads::Threads
${PNG_INCLUDE_DIRS})
target_link_libraries(blueNoiseGen PUBLIC
Threads::Threads
${PNG_LIBRARIES})
target_compile_definitions(blueNoiseGen PRIVATE DITHERING_OPENCL_ENABLED=0)
else()
message(STATUS "OpenCL usage is enabled.")
target_include_directories(blueNoiseGen PUBLIC
Threads::Threads
${OpenCL_INCLUDE_DIRS}
${PNG_INCLUDE_DIRS})
${OpenCL_INCLUDE_DIRS})
target_link_libraries(blueNoiseGen PUBLIC
Threads::Threads
${OpenCL_LIBRARIES}
${PNG_LIBRARIES})
${OpenCL_LIBRARIES})
target_compile_definitions(blueNoiseGen PRIVATE DITHERING_OPENCL_ENABLED=1)
endif()
if(DEFINED DISABLE_VULKAN AND DISABLE_VULKAN)
message(STATUS "Vulkan usage is disabled.")
target_compile_definitions(blueNoiseGen PRIVATE DITHERING_VULKAN_ENABLED=0)
else()
message(STATUS "Vulkan usage is enabled.")
target_include_directories(blueNoiseGen PUBLIC
${Vulkan_INCLUDE_DIRS})
target_link_libraries(blueNoiseGen PUBLIC
${Vulkan_LIBRARIES})
target_compile_definitions(blueNoiseGen PRIVATE DITHERING_VULKAN_ENABLED=1)
if(CMAKE_BUILD_TYPE MATCHES "Debug")
target_compile_definitions(blueNoiseGen PRIVATE VULKAN_VALIDATION=1)
else()
target_compile_definitions(blueNoiseGen PRIVATE VULKAN_VALIDATION=0)
endif()
endif()

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021,2023 Stephen Seo
Copyright (c) 2021,2023-2024 Stephen Seo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -7,6 +7,7 @@ Args::Args()
: generate_blue_noise_(false),
use_opencl_(true),
overwrite_file_(false),
use_vulkan_(true),
blue_noise_size_(32),
threads_(4),
output_filename_("output.png") {}
@ -23,10 +24,12 @@ void Args::DisplayHelp() {
" -t <int> | --threads <int>\t\tUse CPU thread count when "
"not using "
"OpenCL\n"
" -o <filelname> | --output <filename>\tOutput filename to "
" -o <filename> | --output <filename>\tOutput filename to "
"use\n"
" --overwrite\t\t\t\tEnable overwriting of file (default "
"disabled)\n";
"disabled)\n"
" --usevulkan | --nousevulkan\t\t\tUse/Disable Vulkan (enabled "
"by default)\n";
}
bool Args::ParseArgs(int argc, char **argv) {
@ -69,6 +72,10 @@ bool Args::ParseArgs(int argc, char **argv) {
output_filename_ = std::string(argv[1]);
--argc;
++argv;
} else if (std::strcmp(argv[0], "--usevulkan") == 0) {
use_vulkan_ = true;
} else if (std::strcmp(argv[0], "--nousevulkan") == 0) {
use_vulkan_ = false;
} else {
std::cout << "WARNING: Ignoring invalid input \"" << argv[0] << "\""
<< std::endl;

View file

@ -14,6 +14,7 @@ struct Args {
bool generate_blue_noise_;
bool use_opencl_;
bool overwrite_file_;
bool use_vulkan_;
unsigned int blue_noise_size_;
unsigned int threads_;
std::string output_filename_;

File diff suppressed because it is too large Load diff

50
src/blue_noise.glsl Normal file
View file

@ -0,0 +1,50 @@
#version 450
int twoToOne(int x, int y, int width, int height) {
while (x < 0) {
x += width;
}
while (y < 0) {
y += height;
}
x = x % width;
y = y % height;
return x + y * width;
}
layout(binding = 0) readonly buffer PreComputed { float precomputed[]; };
layout(binding = 1) writeonly buffer FilterOut { float filter_out[]; };
layout(binding = 2) readonly buffer PBP { int pbp[]; };
layout(binding = 3) readonly buffer Other {
int width;
int height;
int filter_size;
};
layout(local_size_x = 256) in;
void main() {
uint index = gl_GlobalInvocationID.x;
if (index >= width * height) {
return;
}
int x = int(index % width);
int y = int(index / width);
float sum = 0.0F;
for (int q = 0; q < filter_size; ++q) {
int q_prime = height - filter_size / 2 + y + q;
for (int p = 0; p < filter_size; ++p) {
int p_prime = width - filter_size / 2 + x + p;
if (pbp[twoToOne(p_prime, q_prime, width, height)] != 0) {
sum += precomputed[twoToOne(p, q, filter_size, filter_size)];
}
}
}
filter_out[index] = sum;
}

View file

@ -4,7 +4,9 @@
#if DITHERING_OPENCL_ENABLED == 1
#include <CL/opencl.h>
#endif
#include <sys/sysinfo.h>
#if DITHERING_VULKAN_ENABLED == 1
#include <vulkan/vulkan.h>
#endif
#include <cassert>
#include <chrono>
@ -28,11 +30,123 @@
namespace dither {
image::Bl blue_noise(int width, int height, int threads = 1,
bool use_opencl = true);
bool use_opencl = true, bool use_vulkan = true);
namespace internal {
std::vector<unsigned int> blue_noise_impl(int width, int height,
int threads = 1);
#if DITHERING_VULKAN_ENABLED == 1
struct QueueFamilyIndices {
QueueFamilyIndices();
std::optional<uint32_t> computeFamily;
bool isComplete();
};
QueueFamilyIndices vulkan_find_queue_families(VkPhysicalDevice device);
std::optional<uint32_t> vulkan_find_memory_type(VkPhysicalDevice phys_dev,
uint32_t t_filter,
VkMemoryPropertyFlags props);
bool vulkan_create_buffer(VkDevice device, VkPhysicalDevice phys_dev,
VkDeviceSize size, VkBufferUsageFlags usage,
VkMemoryPropertyFlags props, VkBuffer &buf,
VkDeviceMemory &buf_mem);
void vulkan_copy_buffer(VkDevice device, VkCommandPool command_pool,
VkQueue queue, VkBuffer src_buf, VkBuffer dst_buf,
VkDeviceSize size);
void vulkan_flush_buffer(VkDevice device, VkDeviceMemory memory);
void vulkan_invalidate_buffer(VkDevice device, VkDeviceMemory memory);
std::vector<unsigned int> blue_noise_vulkan_impl(
VkDevice device, VkPhysicalDevice phys_device,
VkCommandBuffer command_buffer, VkCommandPool command_pool, VkQueue queue,
VkBuffer pbp_buf, VkPipeline pipeline, VkPipelineLayout pipeline_layout,
VkDescriptorSet descriptor_set, VkBuffer filter_out_buf, const int width,
const int height);
std::vector<float> vulkan_buf_to_vec(float *mapped, unsigned int size);
inline bool vulkan_get_filter(
VkDevice device, VkCommandBuffer command_buffer, VkCommandPool command_pool,
VkQueue queue, VkBuffer pbp_buf, VkPipeline pipeline,
VkPipelineLayout pipeline_layout, VkDescriptorSet descriptor_set,
VkBuffer filter_out_buf, const int size, std::vector<bool> &pbp,
bool reversed_pbp, const std::size_t global_size, int *pbp_mapped_int,
VkBuffer staging_pbp_buffer, VkDeviceMemory staging_pbp_buffer_mem,
VkDeviceMemory staging_filter_buffer_mem, VkBuffer staging_filter_buffer) {
vkResetCommandBuffer(command_buffer, 0);
if (reversed_pbp) {
for (unsigned int i = 0; i < pbp.size(); ++i) {
pbp_mapped_int[i] = pbp[i] ? 0 : 1;
}
} else {
for (unsigned int i = 0; i < pbp.size(); ++i) {
pbp_mapped_int[i] = pbp[i] ? 1 : 0;
}
}
vulkan_flush_buffer(device, staging_pbp_buffer_mem);
// Copy pbp buffer.
vulkan_copy_buffer(device, command_pool, queue, staging_pbp_buffer, pbp_buf,
size * sizeof(int));
VkCommandBufferBeginInfo begin_info{};
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
if (vkBeginCommandBuffer(command_buffer, &begin_info) != VK_SUCCESS) {
std::clog << "get_filter ERROR: Failed to begin recording compute "
"command buffer!\n";
return false;
}
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE,
pipeline_layout, 0, 1, &descriptor_set, 0, nullptr);
vkCmdDispatch(command_buffer, global_size, 1, 1);
if (vkEndCommandBuffer(command_buffer) != VK_SUCCESS) {
std::clog << "get_filter ERROR: Failed to record compute command buffer!\n";
return false;
}
{
VkSubmitInfo submit_info{};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &command_buffer;
submit_info.signalSemaphoreCount = 0;
submit_info.pSignalSemaphores = nullptr;
if (vkQueueSubmit(queue, 1, &submit_info, nullptr) != VK_SUCCESS) {
std::clog
<< "get_filter ERROR: Failed to submit compute command buffer!\n";
return false;
}
}
if (vkDeviceWaitIdle(device) != VK_SUCCESS) {
std::clog << "get_filter ERROR: Failed to vkDeviceWaitIdle!\n";
return false;
}
// Copy back filter_out buffer.
vulkan_copy_buffer(device, command_pool, queue, filter_out_buf,
staging_filter_buffer, size * sizeof(float));
vulkan_invalidate_buffer(device, staging_filter_buffer_mem);
return true;
}
#endif
#if DITHERING_OPENCL_ENABLED == 1
std::vector<unsigned int> blue_noise_cl_impl(const int width, const int height,
const int filter_size,
@ -265,6 +379,42 @@ inline std::pair<int, int> filter_minmax(const std::vector<float> &filter,
return {min_index, max_index};
}
inline std::pair<int, int> filter_minmax_raw_array(const float *const filter,
unsigned int size,
std::vector<bool> pbp) {
// ensure minority pixel is "true"
unsigned int count = 0;
for (bool value : pbp) {
if (value) {
++count;
}
}
if (count * 2 >= pbp.size()) {
// std::cout << "MINMAX flip\n"; // DEBUG
for (unsigned int i = 0; i < pbp.size(); ++i) {
pbp[i] = !pbp[i];
}
}
float min = std::numeric_limits<float>::infinity();
float max = -std::numeric_limits<float>::infinity();
int min_index = -1;
int max_index = -1;
for (unsigned int i = 0; i < size; ++i) {
if (!pbp[i] && filter[i] < min) {
min_index = i;
min = filter[i];
}
if (pbp[i] && filter[i] > max) {
max_index = i;
max = filter[i];
}
}
return {min_index, max_index};
}
inline std::pair<int, int> filter_abs_minmax(const std::vector<float> &filter) {
float min = std::numeric_limits<float>::infinity();
float max = -std::numeric_limits<float>::infinity();

View file

@ -41,7 +41,7 @@ int main(int argc, char **argv) {
std::cout << "Generating blue_noise..." << std::endl;
image::Bl bl =
dither::blue_noise(args.blue_noise_size_, args.blue_noise_size_,
args.threads_, args.use_opencl_);
args.threads_, args.use_opencl_, args.use_vulkan_);
if (!bl.writeToFile(image::file_type::PNG, args.overwrite_file_,
args.output_filename_)) {
std::cout << "ERROR: Failed to write blue-noise to file\n";

31
src/utility.cpp Normal file
View file

@ -0,0 +1,31 @@
#include "utility.hpp"
utility::Cleanup::Cleanup(std::function<void(void *)> fn, void *ptr)
: fn(fn), ptr(ptr) {}
utility::Cleanup::Cleanup() : fn(), ptr(nullptr) {}
utility::Cleanup::~Cleanup() {
if (this->fn.has_value()) {
this->fn.value()(this->ptr);
}
}
utility::Cleanup::Cleanup(Cleanup &&other) : fn(other.fn), ptr(other.ptr) {
other.fn = std::nullopt;
other.ptr = nullptr;
}
utility::Cleanup &utility::Cleanup::operator=(utility::Cleanup &&other) {
if (this->fn.has_value()) {
this->fn.value()(this->ptr);
}
this->fn = other.fn;
this->ptr = other.ptr;
other.fn = std::nullopt;
other.ptr = nullptr;
return *this;
}

View file

@ -2,6 +2,8 @@
#define DITHERING_UTILITY_HPP
#include <cmath>
#include <functional>
#include <optional>
#include <utility>
namespace utility {
@ -28,6 +30,25 @@ inline float dist(int a, int b, int width) {
float dy = axy.second - bxy.second;
return std::sqrt(dx * dx + dy * dy);
}
class Cleanup {
public:
Cleanup();
Cleanup(std::function<void(void *)> fn, void *ptr);
~Cleanup();
// allow move
Cleanup(Cleanup &&);
Cleanup &operator=(Cleanup &&);
// deny copy
Cleanup(const Cleanup &) = delete;
Cleanup &operator=(const Cleanup &) = delete;
private:
std::optional<std::function<void(void *)>> fn;
void *ptr;
};
} // namespace utility
#endif