/* Copyright (C) 2024 Aiden Gall This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "farbfeld.h" #include "util.h" #include #include #include #include #include #include #include #include #ifndef SPIRV_START # define SPIRV_START _binary_src_cl_spirt_spv_start #endif #ifndef SPIRV_END # define SPIRV_END _binary_src_cl_spirt_spv_end #endif #define SPIRV_SIZE ((size_t)SPIRV_END - (size_t)SPIRV_START) struct ClRuntime { cl_platform_id platform; cl_device_id device; cl_context context; cl_command_queue command_queue; cl_program program; cl_kernel k_hello; }; static cl_platform_id *get_platforms(cl_uint *num_platforms); static cl_device_id *get_devices(cl_platform_id platform, cl_uint *num_devices); static cl_program compile_spirv_program(cl_context context, cl_device_id device, const void *spirv_start, size_t spirv_size); static struct ClRuntime init(size_t platidx, size_t devidx); static uint16_t *run_k_hello(struct ClRuntime *runtime, size_t *canvas_size_ret, size_t image_width, size_t image_height); static void clean(struct ClRuntime *runtime); static long argtonum(const char *arg, long minval, long maxval); extern const char *SPIRV_START[]; extern const char *SPIRV_END[]; static cl_platform_id * get_platforms(cl_uint *const num_platforms) { cl_int err; cl_platform_id *platforms; cl_uint len; err = clGetPlatformIDs(0, NULL, &len); if (err != CL_SUCCESS) die("clGetPlatformIDs: %s\n", cl_strerror(err)); warn("number of platforms = %d\n", len); if (len < 1) die("clGetPlatformIDs: No OpenCL platforms\n"); platforms = calloc(len, sizeof(*platforms)); if (!platforms) die("calloc: Out of memory\n"); err = clGetPlatformIDs(len, platforms, NULL); if (err != CL_SUCCESS) die("clGetPlatformIDs: %s\n", cl_strerror(err)); *num_platforms = len; return platforms; } static cl_device_id * get_devices(const cl_platform_id platform, cl_uint *const num_devices) { cl_int err; cl_device_id *devices; cl_uint len; err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_ALL, 0, NULL, &len); if (err != CL_SUCCESS) die("clGetDeviceIDs: %s\n", cl_strerror(err)); warn("number of devices in platform = %d\n", len); if (len < 1) die("clGetDeviceIDs: No OpenCL devices in platform\n"); devices = calloc(len, sizeof(*devices)); if (!devices) die("calloc: Out of memory\n"); err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_ALL, len, devices, NULL); if (err != CL_SUCCESS) die("clGetDeviceIDs: %s\n", cl_strerror(err)); *num_devices = len; return devices; } static cl_program compile_spirv_program(const cl_context context, const cl_device_id device, const void *const spirv_start, const size_t spirv_size) { cl_int err, build_err; cl_program program; program = clCreateProgramWithIL(context, spirv_start, spirv_size, &err); if (err != CL_SUCCESS) die("clCreateProgramWithIL: %s\n", cl_strerror(err)); build_err = clBuildProgram(program, 1, &device, NULL, NULL, NULL); if (build_err != CL_SUCCESS) { cl_char *log; size_t buffer_size; err = clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &buffer_size); if (err != CL_SUCCESS) die("clGetProgramBuildInfo: %s\n", cl_strerror(err)); if (buffer_size < 1) die("clGetProgramBuildInfo: " "Build log buffer is empty\n"); log = calloc(buffer_size, sizeof(*log)); if (!log) die("calloc: Out of memory\n"); err = clGetProgramBuildInfo( program, device, CL_PROGRAM_BUILD_LOG, sizeof(*log) * buffer_size, log, NULL); if (err != CL_SUCCESS) die("clGetProgramBuildInfo: %s\n", cl_strerror(err)); efwrite(log, sizeof(*log), buffer_size, stderr); free(log); die("\nclBuildProgram: %s\n", cl_strerror(build_err)); } return program; } static struct ClRuntime init(const size_t platidx, const size_t devidx) { cl_int err; cl_platform_id *platforms; cl_device_id *devices; cl_uint num_platforms; cl_uint num_devices; struct ClRuntime runtime; platforms = get_platforms(&num_platforms); if (platidx >= num_platforms) die("get_platforms: Index out of range\n"); runtime.platform = platforms[platidx]; free(platforms); devices = get_devices(runtime.platform, &num_devices); if (devidx >= num_devices) die("get_devices: Index out of range\n"); runtime.device = devices[devidx]; free(devices); runtime.context = clCreateContext(NULL, 1, &runtime.device, NULL, NULL, &err); if (err != CL_SUCCESS) die("clCreateContext: %s\n", cl_strerror(err)); runtime.command_queue = clCreateCommandQueueWithProperties( runtime.context, runtime.device, NULL, &err); if (err != CL_SUCCESS) die("clCreateCommandQueueWithProperties: %s\n", cl_strerror(err)); runtime.program = compile_spirv_program(runtime.context, runtime.device, SPIRV_START, SPIRV_SIZE); runtime.k_hello = clCreateKernel(runtime.program, "hello", &err); if (err != CL_SUCCESS) die("clCreateKernel: %s\n", cl_strerror(err)); return runtime; } static uint16_t * run_k_hello(struct ClRuntime *const runtime, size_t *const canvas_size_ret, const size_t image_width, const size_t image_height) { cl_int err; size_t global_item_size[2]; uint16_t *h_canvas; cl_mem d_canvas; size_t canvas_size; canvas_size = (sizeof("RGBA") - 1) * image_width * image_height; h_canvas = calloc(canvas_size, sizeof(*h_canvas)); if (!h_canvas) die("calloc: Out of memory\n"); d_canvas = clCreateBuffer(runtime->context, CL_MEM_WRITE_ONLY, sizeof(*h_canvas) * canvas_size, NULL, &err); if (err != CL_SUCCESS) die("clCreateBuffer: %s\n", cl_strerror(err)); err = clSetKernelArg(runtime->k_hello, 0, sizeof(d_canvas), &d_canvas); if (err != CL_SUCCESS) die("clSetKernelArg: %s\n", cl_strerror(err)); global_item_size[0] = image_width; global_item_size[1] = image_height; err = clEnqueueNDRangeKernel(runtime->command_queue, runtime->k_hello, 2, NULL, global_item_size, NULL, 0, NULL, NULL); if (err != CL_SUCCESS) die("clEnqueueNDRangeKernel: %s\n", cl_strerror(err)); err = clEnqueueReadBuffer(runtime->command_queue, d_canvas, CL_TRUE, 0, sizeof(*h_canvas) * canvas_size, h_canvas, 0, NULL, NULL); if (err != CL_SUCCESS) die("clEnqueueReadBuffer: %s\n", cl_strerror(err)); clReleaseMemObject(d_canvas); *canvas_size_ret = canvas_size; return h_canvas; } static void clean(struct ClRuntime *const runtime) { clFlush(runtime->command_queue); clFinish(runtime->command_queue); clReleaseKernel(runtime->k_hello); clReleaseProgram(runtime->program); clReleaseCommandQueue(runtime->command_queue); clReleaseContext(runtime->context); } static long argtonum(const char *const arg, const long minval, const long maxval) { long ret; char *endptr; if (minval > maxval) die("argtonum: Invalid range\n"); if (!arg) die("argtonum: Missing argument\n"); errno = 0; ret = strtol(arg, &endptr, 10); if (errno) die("strtol: %s\n", strerror(errno)); if (arg == endptr || *endptr) die("strtol: Invalid integer\n"); if (ret < minval || ret > maxval) die("argtonum: Argument out of range\n"); return ret; } int main(int argc, char *argv[]) { long opt_platidx, opt_devidx, opt_width, opt_height; FILE *fd; struct ClRuntime runtime; uint16_t *h_canvas; size_t canvas_size; opt_platidx = 0; opt_devidx = 0; opt_width = 256; opt_height = 256; if (argv[0]) argv++, argc--; for (; argv[0] && argv[0][0] == '-' && argv[0][1]; argv++, argc--) { char flag; if (argv[0][2]) die("arg_parse: Invalid flag '%s'\n", argv[0]); flag = argv[0][1]; argv++, argc--; switch (flag) { case 'p': opt_platidx = argtonum(argv[0], 0, LONG_MAX); break; case 'd': opt_devidx = argtonum(argv[0], 0, LONG_MAX); break; case 'w': opt_width = argtonum(argv[0], 1, LONG_MAX); break; case 'h': opt_height = argtonum(argv[0], 1, LONG_MAX); break; case '-': goto end_flags; default: die("arg_parse: Unknown flag '-%c'\n", flag); } } end_flags: if (argc > 1) die("arg_parse: Unexpected argument\n"); fd = stdout; if (argv[0]) { errno = 0; fd = fopen(argv[0], "w"); if (errno) die("fopen: %s\n", strerror(errno)); if (!fd) die("fopen: Returned NULL\n"); } runtime = init(opt_platidx, opt_devidx); h_canvas = run_k_hello(&runtime, &canvas_size, opt_width, opt_height); farbfeld_write(fd, h_canvas, canvas_size, opt_width, opt_height); fclose(fd); free(h_canvas); clean(&runtime); return 0; }