commit aee366917834dc1b12fd69e09305036cabd0f05b
Author: Pollux <pollux@pollux.codes>
Date: Sat, 18 Jan 2025 19:54:08 -0600
feat: initial commit
Signed-off-by: Pollux <pollux@pollux.codes>
Diffstat:
A | .pre-commit-config.yaml | | | 17 | +++++++++++++++++ |
A | Makefile | | | 77 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | README.md | | | 37 | +++++++++++++++++++++++++++++++++++++ |
A | example.cl | | | 22 | ++++++++++++++++++++++ |
A | src/runcl.c | | | 235 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
5 files changed, 388 insertions(+), 0 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
@@ -0,0 +1,17 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v5.0.0
+ hooks:
+ - id: check-case-conflict
+ - id: check-illegal-windows-names
+ - id: check-yaml
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+ - repo: https://github.com/pre-commit/mirrors-clang-format
+ rev: v19.1.6
+ hooks:
+ - id: clang-format
+ - repo: https://github.com/commitizen-tools/commitizen
+ rev: v4.1.0
+ hooks:
+ - id: commitizen
diff --git a/Makefile b/Makefile
@@ -0,0 +1,77 @@
+# User-defined variables
+
+PREFIX ?= /usr/local
+BINDIR ?= $(PREFIX)/bin
+INCDIR ?= $(PREFIX)/include
+BINMODE ?= 755
+LIBMODE ?= 644
+DOCMODE ?= 644
+
+BINSUFFIX ?=
+
+# Programs used
+
+PKG_CONFIG := pkg-config
+RM := rm -rf
+MKDIR := mkdir
+INSTALL := install
+
+# Other variables
+
+SRCDIR = src
+OBJDIR = build
+HDIR = include
+
+SRC_FILES = $(shell find $(SRCDIR) -name '*.c' -printf "%P ")
+
+LIBS = "OpenCL >= 3.0 libpng >= 1.6.44"
+OBJS = $(addprefix $(OBJDIR)/,$(SRC_FILES:c=o))
+
+# CFLAGS
+
+debug: CFLAGS = -g -O0
+debug: WFLAGS = -Werror
+release: CFLAGS = -O2
+release: WFLAGS = -Wall -Wextra -Werror -Wpedantic
+
+RCFLAGS = -c $(CFLAGS) $(WFLAGS) -std=c99 $(shell pkg-config --cflags $(LIBS)) -I$(HDIR)
+RLDFLAGS = $(LDFLAGS) $(shell pkg-config --libs $(LIBS))
+
+# Build Info
+
+PROG_NAME = runcl
+VERSION = $(shell git describe --tags --always 2>/dev/null)
+
+BUILD_TYPE = Release
+debug: BUILD_TYPE = Debug
+
+# Preprocessor Flags
+
+CPPFLAGS = -DPROG_NAME=$(PROG_NAME) -DVERSION=$(VERSION) -DBUILD_TYPE=$(BUILD_TYPE) -DCFLAGS="$(RCFLAGS)" -DLDFLAGS="$(RLDFLAGS)"
+
+# Build Targets
+
+all: release
+release: runcl
+debug: runcl
+
+runcl: $(OBJS)
+ @printf "LD $@\n"
+ @$(CC) $(RLDFLAGS) -o runcl $(OBJS)
+
+$(OBJDIR)/%.o: $(SRCDIR)/%.c
+ @$(MKDIR) -p $(@D)
+ @printf "CC $@\n"
+ @$(CC) $(RCFLAGS) $(CPPFLAGS) -o $@ $^
+
+
+clean:
+ @printf "RM $(OBJDIR)\n"
+ @$(RM) $(OBJDIR)
+ @printf "RM runcl\n"
+ @$(RM) runcl
+
+install:
+ @$(INSTALL) -d $(DESTDIR)$(BINDIR)/
+ @printf "INSTALL runcl -> $(DESTDIR)$(BINDIR)/runcl$(BINSUFFIX)\n"
+ @$(INSTALL) -s -m $(BINMODE) runcl $(DESTDIR)$(BINDIR)/runcl$(BINSUFFIX)
diff --git a/README.md b/README.md
@@ -0,0 +1,37 @@
+# runcl - A simple OpenCL harness for rendering images
+
+`runcl` is a minimal OpenCL harness allowing image generation on the GPU by
+means of executing an OpenCL file to generate the image raster data.
+
+`runcl` depends on `libpng` and `libopencl`.
+
+`runcl` was designed to generate fractal art, a few examples of which can be
+found at <https://pollux.codes/art>.
+
+## Installation
+
+```sh
+make
+sudo make install
+```
+
+## Usage
+
+The arguments of `runcl` are mostly self-explanatory:
+```sh
+runcl -p <platform-index> -w <image-width> -h <image-height> input-opencl-file output-png-file
+```
+
+Use `clinfo` to see platform information, by default the platform with index 0
+is chosen.
+
+## CL File Requirements
+
+In order for runcl to work, the OpenCL file used must meet the following
+requirements:
+
+- Kernel named 'render'
+- Arguments 'const int width', 'const int height', and '__global float *out'
+
+See the `example.cl` file for a demonstration of these requirements, as well as
+the suggested way of using the arguments.
diff --git a/example.cl b/example.cl
@@ -0,0 +1,22 @@
+// This is the main function of the program.
+// Runcl passes the following arguments to the program.
+// - width: the width of the image in pixels
+// - height: the height of the image in pixels
+// - *out: the output of the program, formatted as an 3xwxh array of normalized rgm pixel colors
+kernel void render(const int width, const int height, __global float *out)
+{
+ // this gets the id of the particular instance
+ const int i = get_global_id(0);
+
+ if ( i < width * height )
+ {
+ // this converts the id into normalized image coordinates
+ float x = (float)(i % width) / width;
+ float y = (float)(i / width) / height;
+
+ // output the rgb color to the out array
+ out[3*i] = x;
+ out[3*i+1] = x;
+ out[3*i+2] = x;
+ }
+}
diff --git a/src/runcl.c b/src/runcl.c
@@ -0,0 +1,235 @@
+
+#define CL_TARGET_OPENCL_VERSION 300
+#define USAGE \
+ "usage: runcl -p <platform-index> -w <image-width> -h <image-height> " \
+ "input-opencl-file output-png-file"
+
+#define STR_LITERAL(x) #x
+#define STRINGIFY(x) STR_LITERAL(x)
+
+#include <CL/cl.h>
+#include <getopt.h>
+#include <png.h>
+#include <stdio.h>
+#include <unistd.h>
+
+void print_help() { printf(USAGE "\n"); }
+
+void print_version() {
+ printf(STRINGIFY(PROG_NAME) " " STRINGIFY(VERSION) "\n");
+ printf("Build: " STRINGIFY(BUILD_TYPE) "\n");
+ printf("CFLAGS: " STRINGIFY(CFLAGS) "\n");
+ printf("LDFLAGS: " STRINGIFY(LDFLAGS) "\n");
+}
+
+int main(int argc, char **argv) {
+
+ char *source_file = NULL;
+ char *out_file = NULL;
+
+ int img_width = 640;
+ int img_height = 480;
+ int platform_index = 0;
+
+ int c;
+
+ opterr = 0;
+
+ int data_size_out = sizeof(float) * img_width * img_height * 3;
+ float *data_out = (float *)malloc(data_size_out);
+
+ cl_platform_id platform_id = NULL;
+
+ cl_device_id device_id = NULL;
+
+ cl_mem out_buff;
+
+ cl_context context = NULL;
+ cl_command_queue command_queue = NULL;
+ cl_kernel kernel = NULL;
+ cl_program program = NULL;
+
+ char *source_str = NULL;
+ size_t source_size = 0;
+
+ FILE *fp;
+
+ cl_uint platform_count = 256;
+ cl_platform_id *platforms = malloc(platform_count * sizeof(cl_platform_id));
+
+ cl_int err;
+
+ size_t g_work_size, l_work_size;
+
+ char name[100];
+ size_t name_size;
+
+ png_structp png_ptr;
+ png_infop info_ptr;
+ png_bytep row;
+ row = (png_bytep)malloc(3 * img_width * sizeof(png_byte));
+
+ while ((c = getopt(argc, argv, "p:w:h:Hv")) != -1) {
+ switch (c) {
+ case 'p':
+ platform_index = atoi(optarg);
+ break;
+ case 'w':
+ img_width = atoi(optarg);
+ break;
+ case 'h':
+ img_height = atoi(optarg);
+ break;
+ case 'H':
+ print_help();
+ exit(0);
+ break;
+ case 'v':
+ print_version();
+ exit(0);
+ break;
+ case '?':
+ fprintf(stderr, "Error parsing command-line arguments...\n");
+ return 1;
+ }
+ }
+
+ if (argc < optind + 2) {
+ fprintf(stderr, "Error parsing command-line arguments...\n");
+ return 1;
+ }
+
+ source_file = argv[optind];
+ out_file = argv[optind + 1];
+
+ // Load Program Source
+
+ fp = fopen(source_file, "r");
+ if (!fp) {
+ printf("Cannot open file: %s\n", source_file);
+ goto error;
+ }
+
+ source_str = (char *)malloc(1048576);
+ source_size = fread(source_str, 1, 1048576, fp);
+ fclose(fp);
+
+ // Define Vars
+
+ // Get Platform & Device
+ clGetPlatformIDs(0, NULL, &platform_count);
+
+ clGetPlatformIDs(platform_count, platforms, NULL);
+
+ platform_id = platforms[platform_index];
+
+ clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_GPU, 1, &device_id, NULL);
+
+ clGetPlatformInfo(platform_id, CL_PLATFORM_NAME, 100, name, &name_size);
+
+ printf("Platform in Use: %s\n", name);
+
+ // Create Context & Queue
+ context = clCreateContext(NULL, 1, &device_id, NULL, NULL, NULL);
+ command_queue =
+ clCreateCommandQueueWithProperties(context, device_id, 0, NULL);
+
+ // Create Buffers
+ out_buff =
+ clCreateBuffer(context, CL_MEM_WRITE_ONLY, data_size_out, NULL, NULL);
+
+ program = clCreateProgramWithSource(context, 1, (const char **)&source_str,
+ (const size_t *)&source_size, NULL);
+
+ err = clBuildProgram(program, 1, &device_id, NULL, NULL, NULL);
+
+ if (err != CL_SUCCESS) {
+ printf("Failed to build program!\n");
+ char build_log[16348];
+ clGetProgramBuildInfo(program, device_id, CL_PROGRAM_BUILD_LOG,
+ sizeof(build_log), build_log, NULL);
+ printf("Build Error: %s\n", build_log);
+ goto error;
+ }
+
+ kernel = clCreateKernel(program, "render", &err);
+
+ if (err != CL_SUCCESS) {
+ printf("Error creating kernel: %d\n", (int)err);
+ goto error;
+ }
+
+ clSetKernelArg(kernel, 0, sizeof(cl_int), (void *)&img_width);
+ clSetKernelArg(kernel, 1, sizeof(cl_int), (void *)&img_height);
+ clSetKernelArg(kernel, 2, sizeof(cl_mem), (void *)&out_buff);
+
+ l_work_size = 128;
+ g_work_size = (img_width * img_height / l_work_size + 1) * l_work_size;
+
+ err = clEnqueueNDRangeKernel(command_queue, kernel, 1, NULL, &g_work_size,
+ &l_work_size, 0, NULL, NULL);
+
+ if (err != CL_SUCCESS) {
+ printf("Error executing kernel: %d\n", (int)err);
+ goto error;
+ }
+
+ err = clEnqueueReadBuffer(command_queue, out_buff, CL_TRUE, 0, data_size_out,
+ (void *)data_out, 0, NULL, NULL);
+
+ if (err != CL_SUCCESS) {
+ printf("Error reading result: %d\n", (int)err);
+ goto error;
+ }
+
+ // Write PNG
+ fp = fopen(out_file, "wb");
+
+ if (!fp) {
+ printf("Couldn't open output file\n");
+ goto error;
+ }
+
+ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+ info_ptr = png_create_info_struct(png_ptr);
+
+ png_init_io(png_ptr, fp);
+
+ png_set_IHDR(png_ptr, info_ptr, img_width, img_height, 8, PNG_COLOR_TYPE_RGB,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
+ PNG_FILTER_TYPE_BASE);
+
+ png_write_info(png_ptr, info_ptr);
+
+ int x, y;
+ for (y = 0; y < img_height; y++) {
+ for (x = 0; x < img_width; x++) {
+ row[x * 3 + 0] = (int)(255 * data_out[3 * (y * img_width + x) + 0]);
+ row[x * 3 + 1] = (int)(255 * data_out[3 * (y * img_width + x) + 1]);
+ row[x * 3 + 2] = (int)(255 * data_out[3 * (y * img_width + x) + 2]);
+ }
+ png_write_row(png_ptr, row);
+ }
+
+ png_write_end(png_ptr, NULL);
+
+ fclose(fp);
+
+error:
+
+ clFlush(command_queue);
+ clFinish(command_queue);
+
+ clReleaseMemObject(out_buff);
+
+ clReleaseCommandQueue(command_queue);
+ clReleaseContext(context);
+
+ free(platforms);
+ free(source_str);
+ free(data_out);
+ free(row);
+
+ return 0;
+}