-
Notifications
You must be signed in to change notification settings - Fork 0
/
converter.cpp
434 lines (332 loc) · 15 KB
/
converter.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
#include "converter.hpp"
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
// Include the necessary libraries to load an HDR image
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
// Include the necessary libraries to save the radiance and irradiance images in EXR format
#include "tinyexr.h"
// Include Vulkan headers to use the Vulkan (C++) API
#include <vulkan/vulkan.hpp>
using namespace std;
// As is from tinyexr example on github
// See `examples/rgbe2exr/` for more details.
bool SaveEXR(const float* rgb, int width, int height, const char* outfilename) {
EXRHeader header;
InitEXRHeader(&header);
EXRImage image;
InitEXRImage(&image);
image.num_channels = 3;
std::vector<float> images[3];
images[0].resize(width * height);
images[1].resize(width * height);
images[2].resize(width * height);
// Split RGBRGBRGB... into R, G and B layer
for (int i = 0; i < width * height; i++) {
images[0][i] = rgb[3*i+0];
images[1][i] = rgb[3*i+1];
images[2][i] = rgb[3*i+2];
}
float* image_ptr[3];
image_ptr[0] = &(images[2].at(0)); // B
image_ptr[1] = &(images[1].at(0)); // G
image_ptr[2] = &(images[0].at(0)); // R
image.images = (unsigned char**)image_ptr;
image.width = width;
image.height = height;
header.num_channels = 3;
header.channels = (EXRChannelInfo *)malloc(sizeof(EXRChannelInfo) * header.num_channels);
// Must be (A)BGR order, since most of EXR viewers expect this channel order.
strncpy(header.channels[0].name, "B", 255); header.channels[0].name[strlen("B")] = '\0';
strncpy(header.channels[1].name, "G", 255); header.channels[1].name[strlen("G")] = '\0';
strncpy(header.channels[2].name, "R", 255); header.channels[2].name[strlen("R")] = '\0';
header.pixel_types = (int *)malloc(sizeof(int) * header.num_channels);
header.requested_pixel_types = (int *)malloc(sizeof(int) * header.num_channels);
for (int i = 0; i < header.num_channels; i++) {
header.pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; // pixel type of input image
header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_HALF; // pixel type of output image to be stored in .EXR
}
const char* err = NULL; // or nullptr in C++11 or later.
int ret = SaveEXRImageToFile(&image, &header, outfilename, &err);
if (ret != TINYEXR_SUCCESS) {
fprintf(stderr, "Save EXR err: %s\n", err);
FreeEXRErrorMessage(err); // free's buffer for an error message
return ret;
}
printf("Saved exr file. [ %s ] \n", outfilename);
free((void*)rgb);
free(header.channels);
free(header.pixel_types);
free(header.requested_pixel_types);
}
converter::converter(std::string input_file)
{
// We assume that the input file exists and is an HDR image
// Load the HDR image
data = stbi_loadf(input_file.c_str(), &width, &height, &channels, 0);
if (!data)
{
cout << "Error: The input file " << input_file << " is not a valid HDR image." << endl;
return;
}
}
converter::~converter()
{
// Free the memory allocated for the HDR image
stbi_image_free(data);
}
bool converter::process()
{
//////////////////////////////////////////////////////////////////////////
//// Vulkan initialization code
//////////////////////////////////////////////////////////////////////////
vk::ApplicationInfo appInfo = vk::ApplicationInfo()
.setPApplicationName("Vulkan HDR to Radiance Converter")
.setApplicationVersion(1)
.setPEngineName("Vulkan Engine")
.setEngineVersion(1)
.setApiVersion(VK_API_VERSION_1_3);
vk::InstanceCreateInfo instanceCreateInfo = vk::InstanceCreateInfo().setPApplicationInfo(&appInfo);
vk::Instance instance;
try
{
instance = vk::createInstance(instanceCreateInfo);
cout << "Vulkan instance created successfully." << endl;
}
catch (const std::exception& e)
{
std::cerr << "Failed to create Vulkan instance: " << e.what() << std::endl;
return false;
}
auto physicalDevices = instance.enumeratePhysicalDevices();
if (physicalDevices.empty())
{
std::cerr << "No Vulkan physical devices found." << std::endl;
return false;
}
auto physicalDevice = physicalDevices[0];
auto properties = physicalDevice.getProperties();
cout << "Vulkan physical device found: " << properties.deviceName << endl;
uint32_t computeQueueFamilyIndex = -1;
auto queueFamilyProperties = physicalDevice.getQueueFamilyProperties();
for (uint32_t i = 0; i < queueFamilyProperties.size(); i++)
{
if (queueFamilyProperties[i].queueFlags & vk::QueueFlagBits::eCompute)
{
computeQueueFamilyIndex = i;
break;
}
}
if (computeQueueFamilyIndex == -1)
{
std::cerr << "No Vulkan compute queue family found." << std::endl;
return false;
}
float queuePriorities[] = { 1.0f };
vk::DeviceQueueCreateInfo queueCreateInfo = vk::DeviceQueueCreateInfo()
.setQueueFamilyIndex(computeQueueFamilyIndex)
.setQueueCount(1)
.setPQueuePriorities(queuePriorities);
vk::DeviceCreateInfo deviceCreateInfo = vk::DeviceCreateInfo()
.setQueueCreateInfoCount(1)
.setPQueueCreateInfos(&queueCreateInfo);
vk::Device device;
try
{
device = physicalDevice.createDevice(deviceCreateInfo);
cout << "Vulkan device created successfully." << endl;
}
catch (const std::exception& e)
{
std::cerr << "Failed to create Vulkan device: " << e.what() << std::endl;
return false;
}
vk::Queue queue = device.getQueue(computeQueueFamilyIndex, 0);
//////////////////////////////////////////////////////////////////////////
//// Vulkan code to process the HDR image
//////////////////////////////////////////////////////////////////////////
// Create a buffer to store the HDR image
vk::BufferCreateInfo bufferCreateInfo = vk::BufferCreateInfo()
.setSize(width * height * channels * sizeof(float))
.setUsage(vk::BufferUsageFlagBits::eStorageBuffer);
vk::Buffer buffer = device.createBuffer(bufferCreateInfo);
// Allocate memory for the buffer
vk::MemoryRequirements memoryRequirements = device.getBufferMemoryRequirements(buffer);
vk::MemoryAllocateInfo memoryAllocateInfo = vk::MemoryAllocateInfo()
.setAllocationSize(memoryRequirements.size)
.setMemoryTypeIndex(0);
vk::DeviceMemory memory = device.allocateMemory(memoryAllocateInfo);
// Bind the buffer to the memory
device.bindBufferMemory(buffer, memory, 0);
// Copy the HDR image to the buffer
void* mappedMemory = device.mapMemory(memory, 0, VK_WHOLE_SIZE);
memcpy(mappedMemory, data, width * height * channels * sizeof(float));
device.unmapMemory(memory);
//////////////////////////////////////////////////////////////////////////
//// Vulkan code to process the HDR image
//////////////////////////////////////////////////////////////////////////
// The radiance and irradiance images will be calculated and stored as cube
// maps in the following order: radiance positive x, radiance negative x,
// radiance positive y, radiance negative y, radiance positive z, radiance
// negative z, irradiance positive x, irradiance negative x, irradiance
// positive y, irradiance negative y, irradiance positive z, irradiance negative z.
// Per roughness level, the radiance and irradiance images will be stored in
// different layers of the same cube map.
// Calculate the number of roughness levels
int numRoughnessLevels = log2(width) - 1;
// Create a buffer to store the radiance and irradiance images
vk::BufferCreateInfo cubeMapBufferCreateInfo = vk::BufferCreateInfo()
.setSize(width * height * channels * numRoughnessLevels * 12 * sizeof(float))
.setUsage(vk::BufferUsageFlagBits::eStorageBuffer);
vk::Buffer cubeMapBuffer = device.createBuffer(cubeMapBufferCreateInfo);
// Allocate memory for the buffer
vk::MemoryRequirements cubeMapMemoryRequirements = device.getBufferMemoryRequirements(cubeMapBuffer);
vk::MemoryAllocateInfo cubeMapMemoryAllocateInfo = vk::MemoryAllocateInfo()
.setAllocationSize(cubeMapMemoryRequirements.size)
.setMemoryTypeIndex(0);
vk::DeviceMemory cubeMapMemory = device.allocateMemory(cubeMapMemoryAllocateInfo);
// Bind the buffer to the memory
device.bindBufferMemory(cubeMapBuffer, cubeMapMemory, 0);
// Create a descriptor set layout
vk::DescriptorSetLayoutBinding descriptorSetLayoutBinding = vk::DescriptorSetLayoutBinding()
.setBinding(0)
.setDescriptorType(vk::DescriptorType::eStorageBuffer)
.setDescriptorCount(1)
.setStageFlags(vk::ShaderStageFlagBits::eCompute);
vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = vk::DescriptorSetLayoutCreateInfo()
.setBindingCount(1)
.setPBindings(&descriptorSetLayoutBinding);
vk::DescriptorSetLayout descriptorSetLayout = device.createDescriptorSetLayout(descriptorSetLayoutCreateInfo);
// Create a pipeline layout
vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo = vk::PipelineLayoutCreateInfo()
.setSetLayoutCount(1)
.setPSetLayouts(&descriptorSetLayout);
vk::PipelineLayout pipelineLayout = device.createPipelineLayout(pipelineLayoutCreateInfo);
// Create a descriptor pool
vk::DescriptorPoolSize descriptorPoolSize = vk::DescriptorPoolSize()
.setType(vk::DescriptorType::eStorageBuffer)
.setDescriptorCount(2);
vk::DescriptorPoolCreateInfo descriptorPoolCreateInfo = vk::DescriptorPoolCreateInfo()
.setMaxSets(1)
.setPoolSizeCount(1)
.setPPoolSizes(&descriptorPoolSize);
vk::DescriptorPool descriptorPool = device.createDescriptorPool(descriptorPoolCreateInfo);
// Create a descriptor set
vk::DescriptorSetAllocateInfo descriptorSetAllocateInfo = vk::DescriptorSetAllocateInfo()
.setDescriptorPool(descriptorPool)
.setDescriptorSetCount(1)
.setPSetLayouts(&descriptorSetLayout);
vk::DescriptorSet descriptorSet = device.allocateDescriptorSets(descriptorSetAllocateInfo)[0];
// Update the descriptor set
vk::DescriptorBufferInfo descriptorBufferInfo = vk::DescriptorBufferInfo()
.setBuffer(buffer)
.setOffset(0)
.setRange(VK_WHOLE_SIZE);
vk::WriteDescriptorSet writeDescriptorSet = vk::WriteDescriptorSet()
.setDstSet(descriptorSet)
.setDstBinding(0)
.setDescriptorCount(1)
.setDescriptorType(vk::DescriptorType::eStorageBuffer)
.setPBufferInfo(&descriptorBufferInfo);
device.updateDescriptorSets(1, &writeDescriptorSet, 0, nullptr);
// Create a compute shader module
const char* computeShaderCode = R"(
#version 450
layout(set = 0, binding = 0) buffer InputBuffer
{
float data[];
} inputBuffer;
layout(set = 0, binding = 1) buffer OutputBuffer
{
float data[];
} outputBuffer;
void main()
{
uint index = gl_GlobalInvocationID.x;
outputBuffer.data[index] = inputBuffer.data[index];
}
)";
vk::ShaderModuleCreateInfo shaderModuleCreateInfo = vk::ShaderModuleCreateInfo()
.setCodeSize(strlen(computeShaderCode))
.setPCode((uint32_t*)computeShaderCode);
vk::ShaderModule computeShaderModule = device.createShaderModule(shaderModuleCreateInfo);
// Create a compute pipeline
vk::PipelineShaderStageCreateInfo pipelineShaderStageCreateInfo = vk::PipelineShaderStageCreateInfo()
.setStage(vk::ShaderStageFlagBits::eCompute)
.setModule(computeShaderModule)
.setPName("main");
vk::ComputePipelineCreateInfo computePipelineCreateInfo = vk::ComputePipelineCreateInfo()
.setLayout(pipelineLayout)
.setStage(pipelineShaderStageCreateInfo);
auto computePipelineResult = device.createComputePipeline(nullptr, computePipelineCreateInfo);
if (computePipelineResult.result != vk::Result::eSuccess)
{
std::cerr << "Failed to create Vulkan compute pipeline." << std::endl;
return false;
}
vk::Pipeline computePipeline = computePipelineResult.value;
// Create a command pool
vk::CommandPoolCreateInfo commandPoolCreateInfo = vk::CommandPoolCreateInfo()
.setQueueFamilyIndex(computeQueueFamilyIndex);
vk::CommandPool commandPool = device.createCommandPool(commandPoolCreateInfo);
// Create a command buffer
vk::CommandBufferAllocateInfo commandBufferAllocateInfo = vk::CommandBufferAllocateInfo()
.setCommandPool(commandPool)
.setLevel(vk::CommandBufferLevel::ePrimary)
.setCommandBufferCount(1);
vk::CommandBuffer commandBuffer = device.allocateCommandBuffers(commandBufferAllocateInfo)[0];
// Begin the command buffer
vk::CommandBufferBeginInfo commandBufferBeginInfo = vk::CommandBufferBeginInfo()
.setFlags(vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
commandBuffer.begin(commandBufferBeginInfo);
// Bind the pipeline
commandBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, computePipeline);
// Bind the descriptor set
commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
// Dispatch the compute shader
commandBuffer.dispatch(width * height * channels, 1, 1);
// End the command buffer
commandBuffer.end();
// Submit the command buffer
vk::SubmitInfo submitInfo = vk::SubmitInfo()
.setCommandBufferCount(1)
.setPCommandBuffers(&commandBuffer);
queue.submit(1, &submitInfo, nullptr);
queue.waitIdle();
// Destroy the command buffer
device.freeCommandBuffers(commandPool, 1, &commandBuffer);
// Destroy the command pool
device.destroyCommandPool(commandPool);
// Destroy the compute pipeline
device.destroyPipeline(computePipeline);
// Destroy the compute shader module
device.destroyShaderModule(computeShaderModule);
// Destroy the descriptor pool
device.destroyDescriptorPool(descriptorPool);
// Destroy the descriptor set layout
device.destroyDescriptorSetLayout(descriptorSetLayout);
// Destroy the pipeline layout
device.destroyPipelineLayout(pipelineLayout);
// Destroy the buffer
device.destroyBuffer(buffer);
// Free the memory allocated for the buffer
device.freeMemory(memory);
// Destroy the buffer
device.destroyBuffer(cubeMapBuffer);
// Free the memory allocated for the buffer
device.freeMemory(cubeMapMemory);
//////////////////////////////////////////////////////////////////////////
//// Vulkan cleanup code
//////////////////////////////////////////////////////////////////////////
// Destroy the Vulkan device and instance
device.destroy();
instance.destroy();
return true;
}
void converter::save(std::string output_file)
{
// Save the radiance image in EXR format
SaveEXR(data, width, height, output_file.c_str());
}