/* 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 "camera.h"
#include "util.h"
#include "util_cl.h"
#include
#include
#include
#include
#include
#include
#define SPIRV_START _binary_src_cl_spirt_spv_start
#define SPIRV_END _binary_src_cl_spirt_spv_end
#define SPIRV_SIZE ((size_t)SPIRV_END - (size_t)SPIRV_START)
struct kernel_context {
cl_platform_id platform;
cl_device_id device;
cl_context context;
cl_command_queue command_queue;
cl_program program;
cl_kernel k_gen_rays;
cl_kernel k_ray_colour;
};
struct opts {
long width;
long height;
long platidx;
long devidx;
};
static float *kernel_render(struct kernel_context *runtime, size_t image_width,
size_t image_height, struct camera cam);
static struct kernel_context kernel_context_init(size_t platidx, size_t devidx);
static void kernel_context_clean(struct kernel_context *runtime);
static struct opts get_args(int argc, char *argv[]);
extern const char *SPIRV_START[];
extern const char *SPIRV_END[];
static float *
kernel_render(struct kernel_context *const runtime, const size_t image_width,
const size_t image_height, const struct camera cam)
{
cl_int err;
size_t global_item_size[2];
float *h_canvas;
cl_mem d_canvas, d_rays;
size_t rays_size, canvas_size;
canvas_size = (sizeof("RGB") - 1) * image_width * image_height;
rays_size = 2 * image_width * image_height;
d_rays = clCreateBuffer(runtime->context, CL_MEM_READ_WRITE,
sizeof(cl_float3) * rays_size, NULL, &err);
if (err != CL_SUCCESS)
die("clCreateBuffer: %s\n", cl_strerror(err));
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_gen_rays, 0, sizeof(d_rays), &d_rays);
err |= clSetKernelArg(runtime->k_gen_rays, 1, sizeof(cl_float3),
&cam.centre);
err |= clSetKernelArg(runtime->k_gen_rays, 2, sizeof(cl_float3),
&cam.pixel_delta_u);
err |= clSetKernelArg(runtime->k_gen_rays, 3, sizeof(cl_float3),
&cam.pixel_delta_v);
err |= clSetKernelArg(runtime->k_gen_rays, 4, sizeof(cl_float3),
&cam.corner00);
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_gen_rays, 2, NULL,
global_item_size, NULL, 0, NULL, NULL);
if (err != CL_SUCCESS)
die("clEnqueueNDRangeKernel: %s\n", cl_strerror(err));
err = clSetKernelArg(runtime->k_ray_colour, 0, sizeof(d_canvas),
&d_canvas);
err |= clSetKernelArg(runtime->k_ray_colour, 1, sizeof(d_rays),
&d_rays);
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_ray_colour, 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_rays);
clReleaseMemObject(d_canvas);
return h_canvas;
}
static struct kernel_context
kernel_context_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 kernel_context 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_gen_rays = clCreateKernel(runtime.program, "gen_rays", &err);
if (err != CL_SUCCESS)
die("clCreateKernel: %s\n", cl_strerror(err));
runtime.k_ray_colour =
clCreateKernel(runtime.program, "ray_colour", &err);
if (err != CL_SUCCESS)
die("clCreateKernel: %s\n", cl_strerror(err));
return runtime;
}
static void
kernel_context_clean(struct kernel_context *const runtime)
{
clFlush(runtime->command_queue);
clFinish(runtime->command_queue);
clReleaseKernel(runtime->k_gen_rays);
clReleaseKernel(runtime->k_ray_colour);
clReleaseProgram(runtime->program);
clReleaseCommandQueue(runtime->command_queue);
clReleaseContext(runtime->context);
}
static struct opts
get_args(int argc, char *argv[])
{
struct opts opts;
opts.platidx = 0;
opts.devidx = 0;
opts.width = 1024;
opts.height = 576;
if (argv[0])
argv++, argc--;
for (; argv[0] && argv[0][0] == '-' && argv[0][1]; argv++, argc--) {
char flag;
if (argv[0][2])
die("get_args: Invalid flag '%s'\n", argv[0]);
flag = argv[0][1];
argv++, argc--;
switch (flag) {
case 'p':
opts.platidx = argtonum(argv[0], 0, LONG_MAX);
break;
case 'd':
opts.devidx = argtonum(argv[0], 0, LONG_MAX);
break;
case 'w':
opts.width = argtonum(argv[0], 1, LONG_MAX);
break;
case 'h':
opts.height = argtonum(argv[0], 1, LONG_MAX);
break;
case '-':
goto end_flags;
default:
die("get_args: Unknown flag '-%c'\n", flag);
}
}
end_flags:
if (argc > 0)
die("get_args: Unexpected argument\n");
return opts;
}
int
main(int argc, char *argv[])
{
struct opts opts;
struct kernel_context runtime;
float *h_canvas;
struct camera cam;
Image img;
Texture2D texture;
opts = get_args(argc, argv);
runtime = kernel_context_init(opts.platidx, opts.devidx);
cam = camera_init(opts.width, opts.height, 1.0f);
h_canvas = kernel_render(&runtime, opts.width, opts.height, cam);
img.data = h_canvas;
img.width = opts.width;
img.height = opts.height;
img.format = PIXELFORMAT_UNCOMPRESSED_R32G32B32;
img.mipmaps = 1;
InitWindow(opts.width, opts.height, "spirt");
SetTargetFPS(60);
texture = LoadTextureFromImage(img);
while (!WindowShouldClose()) {
BeginDrawing();
ClearBackground(BLACK);
DrawTexture(texture, 0, 0, WHITE);
EndDrawing();
}
UnloadTexture(texture);
CloseWindow();
free(h_canvas);
kernel_context_clean(&runtime);
return 0;
}