实现hdr(偏暗)
parent
4b9232f549
commit
cebfa32c9f
Binary file not shown.
@ -0,0 +1,124 @@
|
||||
#include "BmpLoader.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
BmpInfo BmpLoader::readBmpInfo(const std::string& filePath) {
|
||||
std::ifstream file(filePath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Failed to open BMP file: " + filePath);
|
||||
}
|
||||
|
||||
BmpHeader header;
|
||||
file.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
|
||||
if (header.signature != 0x4D42) { // 'BM'
|
||||
throw std::runtime_error("Invalid BMP signature in file: " + filePath);
|
||||
}
|
||||
|
||||
BmpInfoHeader infoHeader;
|
||||
file.read(reinterpret_cast<char*>(&infoHeader), sizeof(infoHeader));
|
||||
|
||||
BmpInfo info;
|
||||
info.width = infoHeader.width;
|
||||
info.height = std::abs(infoHeader.height); // Handle bottom-up or top-down BMPs
|
||||
info.bitsPerPixel = infoHeader.bitsPerPixel;
|
||||
info.dataOffset = header.dataOffset;
|
||||
|
||||
// Calculate row padding (rows are padded to 4-byte boundary)
|
||||
int bytesPerPixel = info.bitsPerPixel / 8;
|
||||
info.rowSize = info.width * bytesPerPixel;
|
||||
info.rowPadding = (4 - (info.rowSize % 4)) % 4;
|
||||
|
||||
file.close();
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BmpLoader::readBmpRegion(
|
||||
const std::string& filePath,
|
||||
const BmpInfo& info,
|
||||
int32_t startX, int32_t startY,
|
||||
int32_t width, int32_t height) {
|
||||
|
||||
std::ifstream file(filePath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Failed to open BMP file: " + filePath);
|
||||
}
|
||||
|
||||
// Clamp region to image dimensions
|
||||
startX = std::max(0, startX);
|
||||
startY = std::max(0, startY);
|
||||
width = std::min(width, info.width - startX);
|
||||
height = std::min(height, info.height - startY);
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
|
||||
// Calculate bytes per pixel
|
||||
int bytesPerPixel = info.bitsPerPixel / 8;
|
||||
int regionRowSize = width * bytesPerPixel;
|
||||
|
||||
// Allocate memory for the region
|
||||
std::vector<uint8_t> regionData(width * height * bytesPerPixel);
|
||||
|
||||
// Read data row by row
|
||||
for (int y = 0; y < height; y++) {
|
||||
// Calculate source position in file
|
||||
int sourceY = (info.height - 1 - (startY + y)); // Account for BMP being stored bottom-up
|
||||
long offset = info.dataOffset + (sourceY * (info.rowSize + info.rowPadding)) + startX * bytesPerPixel;
|
||||
|
||||
file.seekg(offset, std::ios::beg);
|
||||
file.read(reinterpret_cast<char*>(regionData.data() + y * regionRowSize), regionRowSize);
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
return regionData;
|
||||
}
|
||||
|
||||
// Convert sRGB to linear
|
||||
float srgbToLinear(float srgb) {
|
||||
if (srgb <= 0.04045f)
|
||||
return srgb / 12.92f;
|
||||
else
|
||||
return pow((srgb + 0.055f) / 1.055f, 2.4f);
|
||||
}
|
||||
|
||||
std::vector<float> BmpLoader::readBmpRegionAsFloat(
|
||||
const std::string& filePath,
|
||||
const BmpInfo& info,
|
||||
int32_t startX, int32_t startY,
|
||||
int32_t width, int32_t height) {
|
||||
|
||||
auto data = readBmpRegion(filePath, info, startX, startY, width, height);
|
||||
if (data.empty()) {
|
||||
return std::vector<float>();
|
||||
}
|
||||
|
||||
int bytesPerPixel = info.bitsPerPixel / 8;
|
||||
std::vector<float> floatData(width * height * 3); // Always RGB float output
|
||||
|
||||
// Convert each component from sRGB to linear space
|
||||
#if 0
|
||||
for (int i = 0; i < width * height; i++) {
|
||||
floatData[i * 3 + 0] = srgbToLinear(data[i * bytesPerPixel + 2] / 255.0f); // R
|
||||
floatData[i * 3 + 1] = srgbToLinear(data[i * bytesPerPixel + 1] / 255.0f); // G
|
||||
floatData[i * 3 + 2] = srgbToLinear(data[i * bytesPerPixel + 0] / 255.0f); // B
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < width * height; i++) {
|
||||
if (bytesPerPixel == 3 || bytesPerPixel == 4) { // RGB or RGBA
|
||||
floatData[i * 3 + 0] = data[i * bytesPerPixel + 2] / 255.0f; // R (BGR format in BMP)
|
||||
floatData[i * 3 + 1] = data[i * bytesPerPixel + 1] / 255.0f; // G
|
||||
floatData[i * 3 + 2] = data[i * bytesPerPixel + 0] / 255.0f; // B
|
||||
} else if (bytesPerPixel == 1) { // Grayscale
|
||||
floatData[i * 3 + 0] = data[i] / 255.0f; // R
|
||||
floatData[i * 3 + 1] = data[i] / 255.0f; // G
|
||||
floatData[i * 3 + 2] = data[i] / 255.0f; // B
|
||||
}
|
||||
}
|
||||
|
||||
return floatData;
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct BmpHeader {
|
||||
uint16_t signature; // 'BM'
|
||||
uint32_t fileSize; // Size of the BMP file
|
||||
uint16_t reserved1; // Reserved
|
||||
uint16_t reserved2; // Reserved
|
||||
uint32_t dataOffset; // Offset to the start of image data
|
||||
};
|
||||
|
||||
struct BmpInfoHeader {
|
||||
uint32_t headerSize; // Size of the info header
|
||||
int32_t width; // Width of the image
|
||||
int32_t height; // Height of the image
|
||||
uint16_t planes; // Number of color planes
|
||||
uint16_t bitsPerPixel; // Bits per pixel
|
||||
uint32_t compression; // Compression type
|
||||
uint32_t imageSize; // Image size in bytes
|
||||
int32_t xPixelsPerMeter; // X resolution
|
||||
int32_t yPixelsPerMeter; // Y resolution
|
||||
uint32_t colorsUsed; // Number of colors used
|
||||
uint32_t colorsImportant;// Number of important colors
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct BmpInfo {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
int32_t bitsPerPixel;
|
||||
uint32_t dataOffset;
|
||||
int32_t rowPadding;
|
||||
int32_t rowSize;
|
||||
};
|
||||
|
||||
class BmpLoader {
|
||||
public:
|
||||
static BmpInfo readBmpInfo(const std::string& filePath);
|
||||
static std::vector<uint8_t> readBmpRegion(
|
||||
const std::string& filePath,
|
||||
const BmpInfo& info,
|
||||
int32_t startX, int32_t startY,
|
||||
int32_t width, int32_t height);
|
||||
static std::vector<float> readBmpRegionAsFloat(
|
||||
const std::string& filePath,
|
||||
const BmpInfo& info,
|
||||
int32_t startX, int32_t startY,
|
||||
int32_t width, int32_t height);
|
||||
};
|
@ -0,0 +1,170 @@
|
||||
//
|
||||
// Created by Matthew on 2025/3/22.
|
||||
//
|
||||
|
||||
#include "HdrWriter.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
// Define STB_IMAGE_WRITE_IMPLEMENTATION in exactly one CPP file
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "stb_image_write.h" // Download this header from https://github.com/nothings/stb
|
||||
|
||||
float toneMap(float value) {
|
||||
return value / (1.0f + value); // Reinhard tone mapping
|
||||
}
|
||||
float applyGammaCorrection(float value) {
|
||||
return pow(value, 1.0f / 2.2f); // Gamma correction
|
||||
}
|
||||
|
||||
bool HdrWriter::writeRGBE(const std::string& filename,
|
||||
const std::vector<float>& data,
|
||||
int width, int height) {
|
||||
std::ofstream file(filename, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
writeHeader(file, width, height);
|
||||
|
||||
// Write pixel data in RGBE format
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int pixelIndex = (y * width + x) * 3;
|
||||
float r = data[pixelIndex];
|
||||
float g = data[pixelIndex + 1];
|
||||
float b = data[pixelIndex + 2];
|
||||
|
||||
uint8_t rgbe[4];
|
||||
rgbeFromFloat(r, g, b, rgbe);
|
||||
file.write(reinterpret_cast<char*>(rgbe), 4);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HdrWriter::writeHeader(std::ofstream& file, int width, int height) {
|
||||
// Write Radiance HDR header
|
||||
file << "#?RADIANCE\n";
|
||||
file << "FORMAT=32-bit_rle_rgbe\n\n";
|
||||
file << "-Y " << height << " +X " << width << "\n";
|
||||
}
|
||||
|
||||
void HdrWriter::rgbeFromFloat(float r, float g, float b, uint8_t rgbe[4]) {
|
||||
float v = std::max(r, std::max(g, b));
|
||||
|
||||
if (v < 1e-32f) {
|
||||
rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
|
||||
} else {
|
||||
int e;
|
||||
v = frexpf(v, &e) * 256.0f / v;
|
||||
rgbe[0] = static_cast<uint8_t>(r * v);
|
||||
rgbe[1] = static_cast<uint8_t>(g * v);
|
||||
rgbe[2] = static_cast<uint8_t>(b * v);
|
||||
rgbe[3] = static_cast<uint8_t>(e + 128);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool HdrWriter::writeRGB(const std::string& filename,
|
||||
const std::vector<float>& data,
|
||||
int width, int height,
|
||||
Format format) {
|
||||
|
||||
if (format == Format::BMP) {
|
||||
return writeBMP(filename, data, width, height);
|
||||
}
|
||||
|
||||
// For PNG/JPEG via stb_image_write.h
|
||||
// (Add this implementation if needed)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HdrWriter::writeBMP(const std::string& filename,
|
||||
const std::vector<float>& data,
|
||||
int width, int height) {
|
||||
std::ofstream file(filename, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// BMP requires rows to be padded to multiples of 4 bytes
|
||||
int paddingSize = (4 - (width * 3) % 4) % 4;
|
||||
int rowSize = width * 3 + paddingSize;
|
||||
int dataSize = rowSize * height;
|
||||
int fileSize = 54 + dataSize; // 54 bytes for header + pixel data
|
||||
|
||||
// BMP File Header (14 bytes)
|
||||
uint8_t fileHeader[14] = {
|
||||
'B', 'M', // Signature
|
||||
(uint8_t)(fileSize), (uint8_t)(fileSize >> 8), // File size in bytes
|
||||
(uint8_t)(fileSize >> 16), (uint8_t)(fileSize >> 24),
|
||||
0, 0, 0, 0, // Reserved
|
||||
54, 0, 0, 0 // Offset to pixel data
|
||||
};
|
||||
|
||||
// BMP Info Header (40 bytes)
|
||||
uint8_t infoHeader[40] = {
|
||||
40, 0, 0, 0, // Info header size
|
||||
(uint8_t)(width), (uint8_t)(width >> 8), // Width
|
||||
(uint8_t)(width >> 16), (uint8_t)(width >> 24),
|
||||
(uint8_t)(height), (uint8_t)(height >> 8), // Height (negative for top-down)
|
||||
(uint8_t)(height >> 16), (uint8_t)(height >> 24),
|
||||
1, 0, // Number of color planes
|
||||
24, 0, // Bits per pixel (24 for RGB)
|
||||
0, 0, 0, 0, // No compression
|
||||
0, 0, 0, 0, // Image size (can be 0 for no compression)
|
||||
0, 0, 0, 0, // X pixels per meter
|
||||
0, 0, 0, 0, // Y pixels per meter
|
||||
0, 0, 0, 0, // Total colors (default)
|
||||
0, 0, 0, 0 // Important colors (default)
|
||||
};
|
||||
|
||||
file.write(reinterpret_cast<char*>(fileHeader), 14);
|
||||
file.write(reinterpret_cast<char*>(infoHeader), 40);
|
||||
|
||||
// Padding bytes (zeros)
|
||||
std::vector<uint8_t> padding(paddingSize, 0);
|
||||
|
||||
// Write pixel data (BGR order, bottom-to-top for standard BMP)
|
||||
for (int y = height - 1; y >= 0; y--) { // BMP stores rows bottom-to-top
|
||||
for (int x = 0; x < width; x++) {
|
||||
int pixelIndex = (y * width + x) * 3;
|
||||
|
||||
// Apply tone mapping and gamma correction
|
||||
#if 0
|
||||
float r = toneMap(data[pixelIndex + 0]);
|
||||
float g = toneMap(data[pixelIndex + 1]);
|
||||
float b = toneMap(data[pixelIndex + 2]);
|
||||
#endif
|
||||
|
||||
float exposure = 1.5f; // Adjust this value as needed
|
||||
float r = toneMap(data[pixelIndex + 0] * exposure);
|
||||
float g = toneMap(data[pixelIndex + 1] * exposure);
|
||||
float b = toneMap(data[pixelIndex + 2] * exposure);
|
||||
|
||||
r = applyGammaCorrection(r);
|
||||
g = applyGammaCorrection(g);
|
||||
b = applyGammaCorrection(b);
|
||||
|
||||
// Clamp and convert to byte
|
||||
uint8_t pixelData[3] = {
|
||||
static_cast<uint8_t>(std::min(1.0f, b) * 255.0f), // B
|
||||
static_cast<uint8_t>(std::min(1.0f, g) * 255.0f), // G
|
||||
static_cast<uint8_t>(std::min(1.0f, r) * 255.0f) // R
|
||||
};
|
||||
|
||||
file.write(reinterpret_cast<char*>(pixelData), 3);
|
||||
}
|
||||
|
||||
// Write padding bytes
|
||||
if (paddingSize > 0) {
|
||||
file.write(reinterpret_cast<char*>(padding.data()), paddingSize);
|
||||
}
|
||||
}
|
||||
|
||||
return file.good();
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
//
|
||||
// Created by Matthew on 2025/3/22.
|
||||
//
|
||||
|
||||
#ifndef MPPREVIEW_HDRWRITER_H
|
||||
#define MPPREVIEW_HDRWRITER_H
|
||||
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
|
||||
class HdrWriter {
|
||||
public:
|
||||
static bool writeRGBE(const std::string& filename,
|
||||
const std::vector<float>& data,
|
||||
int width, int height);
|
||||
|
||||
// New RGB file format support
|
||||
enum class Format {
|
||||
PNG,
|
||||
JPEG,
|
||||
BMP
|
||||
};
|
||||
|
||||
static bool writeRGB(const std::string& filename,
|
||||
const std::vector<float>& data,
|
||||
int width, int height,
|
||||
Format format = Format::PNG);
|
||||
|
||||
private:
|
||||
static void writeHeader(std::ofstream& file, int width, int height);
|
||||
static void rgbeFromFloat(float r, float g, float b, uint8_t rgbe[4]);
|
||||
static bool writeBMP(const std::string& filename,
|
||||
const std::vector<float>& data,
|
||||
int width, int height);
|
||||
};
|
||||
|
||||
#endif //MPPREVIEW_HDRWRITER_H
|
@ -0,0 +1,72 @@
|
||||
#version 450
|
||||
|
||||
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
|
||||
|
||||
// Input images packed into one buffer
|
||||
layout(set = 0, binding = 0) buffer InputBuffer {
|
||||
float data[];
|
||||
} inputImages;
|
||||
|
||||
// Output HDR image
|
||||
layout(set = 0, binding = 1) buffer OutputBuffer {
|
||||
vec4 pixels[]; // Make sure this matches your C++ expectations
|
||||
} outputImage;
|
||||
|
||||
// Check if parameter layout matches C++ struct
|
||||
layout(set = 0, binding = 2) uniform Params {
|
||||
uint imageCount;
|
||||
uint width;
|
||||
uint height;
|
||||
float exposureValues[16]; // Must match C++ struct size
|
||||
} params;
|
||||
|
||||
void main() {
|
||||
uint x = gl_GlobalInvocationID.x;
|
||||
uint y = gl_GlobalInvocationID.y;
|
||||
|
||||
if (x >= params.width || y >= params.height)
|
||||
return;
|
||||
|
||||
uint pixelIndex = y * params.width + x;
|
||||
uint pixelsPerImage = params.width * params.height;
|
||||
|
||||
// Debug RGB values for the first image
|
||||
float r0 = inputImages.data[pixelIndex * 3];
|
||||
float g0 = inputImages.data[pixelIndex * 3 + 1];
|
||||
float b0 = inputImages.data[pixelIndex * 3 + 2];
|
||||
|
||||
// HDR merging logic - weighted average with exposure
|
||||
vec3 hdrPixel = vec3(0.0);
|
||||
float weightSum = 0.0;
|
||||
|
||||
for (uint i = 0; i < params.imageCount; i++) {
|
||||
// FIXED: Correct buffer access pattern for packed images
|
||||
uint baseOffset = i * pixelsPerImage * 3;
|
||||
float r = inputImages.data[baseOffset + pixelIndex * 3 + 0];
|
||||
float g = inputImages.data[baseOffset + pixelIndex * 3 + 1];
|
||||
float b = inputImages.data[baseOffset + pixelIndex * 3 + 2];
|
||||
|
||||
vec3 rgb = vec3(r, g, b);
|
||||
|
||||
// Calculate luminance for weighting
|
||||
float lum = dot(rgb, vec3(0.2126, 0.7152, 0.0722));
|
||||
|
||||
// Well-exposed pixels get higher weight
|
||||
float weight = 1.0 - pow(abs(lum - 0.5) * 2.0, 2.0);
|
||||
weight = max(weight, 0.001);
|
||||
|
||||
// Apply exposure value
|
||||
float exposureFactor = params.exposureValues[i];
|
||||
|
||||
hdrPixel += rgb * weight * exposureFactor;
|
||||
weightSum += weight;
|
||||
}
|
||||
|
||||
// Apply stronger exposure boost directly in the shader
|
||||
// hdrPixel *= 2.5; // Boost by 2.5x
|
||||
// Normalize
|
||||
hdrPixel = hdrPixel / max(weightSum, 0.001);
|
||||
|
||||
// Store the result
|
||||
outputImage.pixels[pixelIndex] = vec4(hdrPixel, 1.0);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,704 @@
|
||||
//
|
||||
// Created by Matthew on 2025/3/22.
|
||||
//
|
||||
|
||||
#include "vulkan_hdr_generator.h"
|
||||
|
||||
#include "vulkan_hdr_generator.h"
|
||||
#include "HdrWriter.h"
|
||||
#include <stdexcept>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#define MP_TAG "VOLKAN"
|
||||
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, MP_TAG,__VA_ARGS__)
|
||||
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, MP_TAG,__VA_ARGS__)
|
||||
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, MP_TAG, __VA_ARGS__)
|
||||
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, MP_TAG, __VA_ARGS__)
|
||||
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, MP_TAG,__VA_ARGS__)
|
||||
|
||||
VulkanHdrGenerator::VulkanHdrGenerator(const std::string& compFile) : compFilePath(compFile) {
|
||||
|
||||
// Add initializers for all Vulkan handles
|
||||
instance = VK_NULL_HANDLE;
|
||||
physicalDevice = VK_NULL_HANDLE;
|
||||
device = VK_NULL_HANDLE;
|
||||
computeQueue = VK_NULL_HANDLE;
|
||||
commandPool = VK_NULL_HANDLE;
|
||||
descriptorPool = VK_NULL_HANDLE;
|
||||
commandBuffer = VK_NULL_HANDLE;
|
||||
|
||||
inputBuffer = VK_NULL_HANDLE;
|
||||
inputBufferMemory = VK_NULL_HANDLE;
|
||||
outputBuffer = VK_NULL_HANDLE;
|
||||
outputBufferMemory = VK_NULL_HANDLE;
|
||||
paramsBuffer = VK_NULL_HANDLE;
|
||||
paramsBufferMemory = VK_NULL_HANDLE;
|
||||
|
||||
computeShaderModule = VK_NULL_HANDLE;
|
||||
computePipeline = VK_NULL_HANDLE;
|
||||
pipelineLayout = VK_NULL_HANDLE;
|
||||
descriptorSetLayout = VK_NULL_HANDLE;
|
||||
descriptorSet = VK_NULL_HANDLE;
|
||||
|
||||
setupVulkan();
|
||||
createComputeResources();
|
||||
}
|
||||
|
||||
VulkanHdrGenerator::~VulkanHdrGenerator() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
void VulkanHdrGenerator::setupVulkan() {
|
||||
// Create Vulkan instance
|
||||
VkApplicationInfo appInfo{};
|
||||
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||
appInfo.pApplicationName = "HDR Generator";
|
||||
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
|
||||
appInfo.pEngineName = "No Engine";
|
||||
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
|
||||
appInfo.apiVersion = VK_API_VERSION_1_0;
|
||||
|
||||
VkInstanceCreateInfo createInfo{};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||||
createInfo.pApplicationInfo = &appInfo;
|
||||
|
||||
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create Vulkan instance");
|
||||
}
|
||||
|
||||
// Select physical device
|
||||
uint32_t deviceCount = 0;
|
||||
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
|
||||
|
||||
if (deviceCount == 0) {
|
||||
throw std::runtime_error("Failed to find GPUs with Vulkan support");
|
||||
}
|
||||
|
||||
std::vector<VkPhysicalDevice> devices(deviceCount);
|
||||
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
|
||||
|
||||
// Just use the first device
|
||||
physicalDevice = devices[0];
|
||||
|
||||
// Find compute queue family index
|
||||
computeQueueFamilyIndex = findComputeQueueFamily(physicalDevice);
|
||||
|
||||
// Create logical device
|
||||
VkDeviceQueueCreateInfo queueCreateInfo{};
|
||||
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
queueCreateInfo.queueFamilyIndex = computeQueueFamilyIndex;
|
||||
queueCreateInfo.queueCount = 1;
|
||||
float queuePriority = 1.0f;
|
||||
queueCreateInfo.pQueuePriorities = &queuePriority;
|
||||
|
||||
VkPhysicalDeviceFeatures deviceFeatures{};
|
||||
|
||||
VkDeviceCreateInfo deviceCreateInfo{};
|
||||
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||
deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
|
||||
deviceCreateInfo.queueCreateInfoCount = 1;
|
||||
deviceCreateInfo.pEnabledFeatures = &deviceFeatures;
|
||||
|
||||
if (vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create logical device");
|
||||
}
|
||||
|
||||
vkGetDeviceQueue(device, computeQueueFamilyIndex, 0, &computeQueue);
|
||||
|
||||
// Create command pool
|
||||
VkCommandPoolCreateInfo poolInfo{};
|
||||
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
poolInfo.queueFamilyIndex = computeQueueFamilyIndex;
|
||||
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||
|
||||
if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create command pool");
|
||||
}
|
||||
|
||||
// Create descriptor pool
|
||||
VkDescriptorPoolSize poolSizes[2];
|
||||
poolSizes[0].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
poolSizes[0].descriptorCount = 3; // Input, output, params
|
||||
poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
poolSizes[1].descriptorCount = 1;
|
||||
|
||||
VkDescriptorPoolCreateInfo descriptorPoolInfo{};
|
||||
descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||
descriptorPoolInfo.poolSizeCount = 2;
|
||||
descriptorPoolInfo.pPoolSizes = poolSizes;
|
||||
descriptorPoolInfo.maxSets = 1;
|
||||
|
||||
if (vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create descriptor pool");
|
||||
}
|
||||
|
||||
// Allocate command buffer
|
||||
VkCommandBufferAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
allocInfo.commandPool = commandPool;
|
||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
allocInfo.commandBufferCount = 1;
|
||||
|
||||
if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to allocate command buffer");
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanHdrGenerator::createComputeResources() {
|
||||
// Create descriptor set layout
|
||||
VkDescriptorSetLayoutBinding bindings[3];
|
||||
|
||||
bindings[0].binding = 0;
|
||||
bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
bindings[0].descriptorCount = 1;
|
||||
bindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
bindings[0].pImmutableSamplers = nullptr;
|
||||
|
||||
bindings[1].binding = 1;
|
||||
bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
bindings[1].descriptorCount = 1;
|
||||
bindings[1].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
bindings[1].pImmutableSamplers = nullptr;
|
||||
|
||||
bindings[2].binding = 2;
|
||||
bindings[2].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
bindings[2].descriptorCount = 1;
|
||||
bindings[2].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
bindings[2].pImmutableSamplers = nullptr;
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo layoutInfo{};
|
||||
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
||||
layoutInfo.bindingCount = 3;
|
||||
layoutInfo.pBindings = bindings;
|
||||
|
||||
if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create descriptor set layout");
|
||||
}
|
||||
|
||||
// Create pipeline layout
|
||||
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
|
||||
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||
pipelineLayoutInfo.setLayoutCount = 1;
|
||||
pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
|
||||
|
||||
if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create pipeline layout");
|
||||
}
|
||||
|
||||
// Load shader
|
||||
std::vector<uint8_t> shaderCode = readFile(compFilePath);
|
||||
VkShaderModule shaderModule = createShaderModule(shaderCode);
|
||||
|
||||
// Create compute pipeline
|
||||
VkPipelineShaderStageCreateInfo shaderStageInfo{};
|
||||
shaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
shaderStageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
shaderStageInfo.module = shaderModule;
|
||||
shaderStageInfo.pName = "main";
|
||||
|
||||
VkComputePipelineCreateInfo pipelineInfo{};
|
||||
pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
|
||||
pipelineInfo.stage = shaderStageInfo;
|
||||
pipelineInfo.layout = pipelineLayout;
|
||||
|
||||
if (vkCreateComputePipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &computePipeline) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create compute pipeline");
|
||||
}
|
||||
|
||||
vkDestroyShaderModule(device, shaderModule, nullptr);
|
||||
}
|
||||
|
||||
void VulkanHdrGenerator::createBuffers(VkDeviceSize inputSize, VkDeviceSize outputSize) {
|
||||
// Create input buffer
|
||||
VkBufferCreateInfo inputBufferInfo{};
|
||||
inputBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
inputBufferInfo.size = inputSize;
|
||||
inputBufferInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
inputBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
|
||||
if (vkCreateBuffer(device, &inputBufferInfo, nullptr, &inputBuffer) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create input buffer");
|
||||
}
|
||||
|
||||
VkMemoryRequirements inputMemRequirements;
|
||||
vkGetBufferMemoryRequirements(device, inputBuffer, &inputMemRequirements);
|
||||
|
||||
VkMemoryAllocateInfo inputAllocInfo{};
|
||||
inputAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
inputAllocInfo.allocationSize = inputMemRequirements.size;
|
||||
inputAllocInfo.memoryTypeIndex = findMemoryType(inputMemRequirements.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
|
||||
if (vkAllocateMemory(device, &inputAllocInfo, nullptr, &inputBufferMemory) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to allocate input buffer memory");
|
||||
}
|
||||
|
||||
vkBindBufferMemory(device, inputBuffer, inputBufferMemory, 0);
|
||||
|
||||
// Create output buffer
|
||||
VkBufferCreateInfo outputBufferInfo{};
|
||||
outputBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
outputBufferInfo.size = outputSize;
|
||||
outputBufferInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
outputBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
|
||||
if (vkCreateBuffer(device, &outputBufferInfo, nullptr, &outputBuffer) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create output buffer");
|
||||
}
|
||||
|
||||
VkMemoryRequirements outputMemRequirements;
|
||||
vkGetBufferMemoryRequirements(device, outputBuffer, &outputMemRequirements);
|
||||
|
||||
VkMemoryAllocateInfo outputAllocInfo{};
|
||||
outputAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
outputAllocInfo.allocationSize = outputMemRequirements.size;
|
||||
outputAllocInfo.memoryTypeIndex = findMemoryType(outputMemRequirements.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
|
||||
if (vkAllocateMemory(device, &outputAllocInfo, nullptr, &outputBufferMemory) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to allocate output buffer memory");
|
||||
}
|
||||
|
||||
vkBindBufferMemory(device, outputBuffer, outputBufferMemory, 0);
|
||||
|
||||
// Create params buffer
|
||||
VkBufferCreateInfo paramsBufferInfo{};
|
||||
paramsBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
paramsBufferInfo.size = sizeof(HdrMergeParams);
|
||||
paramsBufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
|
||||
paramsBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
|
||||
if (vkCreateBuffer(device, ¶msBufferInfo, nullptr, ¶msBuffer) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create params buffer");
|
||||
}
|
||||
|
||||
VkMemoryRequirements paramsMemRequirements;
|
||||
vkGetBufferMemoryRequirements(device, paramsBuffer, ¶msMemRequirements);
|
||||
|
||||
VkMemoryAllocateInfo paramsAllocInfo{};
|
||||
paramsAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
paramsAllocInfo.allocationSize = paramsMemRequirements.size;
|
||||
paramsAllocInfo.memoryTypeIndex = findMemoryType(paramsMemRequirements.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
|
||||
if (vkAllocateMemory(device, ¶msAllocInfo, nullptr, ¶msBufferMemory) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to allocate params buffer memory");
|
||||
}
|
||||
|
||||
vkBindBufferMemory(device, paramsBuffer, paramsBufferMemory, 0);
|
||||
}
|
||||
|
||||
void VulkanHdrGenerator::createDescriptorSet() {
|
||||
// Allocate descriptor set
|
||||
VkDescriptorSetAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
allocInfo.descriptorPool = descriptorPool;
|
||||
allocInfo.descriptorSetCount = 1;
|
||||
allocInfo.pSetLayouts = &descriptorSetLayout;
|
||||
|
||||
if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to allocate descriptor set");
|
||||
}
|
||||
|
||||
// Update descriptor set
|
||||
VkDescriptorBufferInfo inputBufferInfo{};
|
||||
inputBufferInfo.buffer = inputBuffer;
|
||||
inputBufferInfo.offset = 0;
|
||||
inputBufferInfo.range = VK_WHOLE_SIZE;
|
||||
|
||||
VkDescriptorBufferInfo outputBufferInfo{};
|
||||
outputBufferInfo.buffer = outputBuffer;
|
||||
outputBufferInfo.offset = 0;
|
||||
outputBufferInfo.range = VK_WHOLE_SIZE;
|
||||
|
||||
VkDescriptorBufferInfo paramsBufferInfo{};
|
||||
paramsBufferInfo.buffer = paramsBuffer;
|
||||
paramsBufferInfo.offset = 0;
|
||||
paramsBufferInfo.range = VK_WHOLE_SIZE;
|
||||
|
||||
VkWriteDescriptorSet descriptorWrites[3];
|
||||
|
||||
descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
descriptorWrites[0].dstSet = descriptorSet;
|
||||
descriptorWrites[0].dstBinding = 0;
|
||||
descriptorWrites[0].dstArrayElement = 0;
|
||||
descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
descriptorWrites[0].descriptorCount = 1;
|
||||
descriptorWrites[0].pBufferInfo = &inputBufferInfo;
|
||||
descriptorWrites[0].pImageInfo = nullptr;
|
||||
descriptorWrites[0].pTexelBufferView = nullptr;
|
||||
descriptorWrites[0].pNext = nullptr;
|
||||
|
||||
descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
descriptorWrites[1].dstSet = descriptorSet;
|
||||
descriptorWrites[1].dstBinding = 1;
|
||||
descriptorWrites[1].dstArrayElement = 0;
|
||||
descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
descriptorWrites[1].descriptorCount = 1;
|
||||
descriptorWrites[1].pBufferInfo = &outputBufferInfo;
|
||||
descriptorWrites[1].pImageInfo = nullptr;
|
||||
descriptorWrites[1].pTexelBufferView = nullptr;
|
||||
descriptorWrites[1].pNext = nullptr;
|
||||
|
||||
descriptorWrites[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
descriptorWrites[2].dstSet = descriptorSet;
|
||||
descriptorWrites[2].dstBinding = 2;
|
||||
descriptorWrites[2].dstArrayElement = 0;
|
||||
descriptorWrites[2].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
descriptorWrites[2].descriptorCount = 1;
|
||||
descriptorWrites[2].pBufferInfo = ¶msBufferInfo;
|
||||
descriptorWrites[2].pImageInfo = nullptr;
|
||||
descriptorWrites[2].pTexelBufferView = nullptr;
|
||||
descriptorWrites[2].pNext = nullptr;
|
||||
|
||||
vkUpdateDescriptorSets(device, 3, descriptorWrites, 0, nullptr);
|
||||
}
|
||||
|
||||
void VulkanHdrGenerator::processImageBatch(
|
||||
const std::vector<std::vector<float>>& images,
|
||||
std::vector<float>& outputData,
|
||||
int32_t width, int32_t height,
|
||||
const std::vector<float>& exposures) {
|
||||
|
||||
if (images.empty() || width <= 0 || height <= 0 || exposures.empty()) {
|
||||
ALOGE("Invalid input parameters to processImageBatch");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t imageCount = images.size();
|
||||
size_t pixelCount = width * height;
|
||||
size_t inputBufferSize = imageCount * pixelCount * 3 * sizeof(float);
|
||||
size_t outputBufferSize = pixelCount * 4 * sizeof(float);
|
||||
|
||||
// Create buffers if they don't exist or need resizing
|
||||
static VkDeviceSize lastInputSize = 0;
|
||||
static VkDeviceSize lastOutputSize = 0;
|
||||
|
||||
if (lastInputSize != inputBufferSize || lastOutputSize != outputBufferSize) {
|
||||
// Clean up old buffers if they exist
|
||||
if (lastInputSize > 0) {
|
||||
// Free descriptor set first before destroying resources it references
|
||||
if (descriptorSet != VK_NULL_HANDLE) {
|
||||
vkFreeDescriptorSets(device, descriptorPool, 1, &descriptorSet);
|
||||
descriptorSet = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
vkDestroyBuffer(device, inputBuffer, nullptr);
|
||||
vkFreeMemory(device, inputBufferMemory, nullptr);
|
||||
vkDestroyBuffer(device, outputBuffer, nullptr);
|
||||
vkFreeMemory(device, outputBufferMemory, nullptr);
|
||||
vkDestroyBuffer(device, paramsBuffer, nullptr);
|
||||
vkFreeMemory(device, paramsBufferMemory, nullptr);
|
||||
}
|
||||
|
||||
createBuffers(inputBufferSize, outputBufferSize);
|
||||
createDescriptorSet();
|
||||
|
||||
lastInputSize = inputBufferSize;
|
||||
lastOutputSize = outputBufferSize;
|
||||
}
|
||||
|
||||
// Upload input images
|
||||
void* inputData;
|
||||
vkMapMemory(device, inputBufferMemory, 0, inputBufferSize, 0, &inputData);
|
||||
|
||||
float* floatPtr = static_cast<float*>(inputData);
|
||||
for (size_t i = 0; i < imageCount; i++) {
|
||||
std::memcpy(
|
||||
floatPtr + i * pixelCount * 3,
|
||||
images[i].data(),
|
||||
pixelCount * 3 * sizeof(float)
|
||||
);
|
||||
}
|
||||
|
||||
vkUnmapMemory(device, inputBufferMemory);
|
||||
|
||||
// Upload parameters
|
||||
HdrMergeParams params{};
|
||||
params.imageCount = static_cast<uint32_t>(imageCount);
|
||||
params.width = width;
|
||||
params.height = height;
|
||||
|
||||
for (size_t i = 0; i < imageCount && i < 16; i++) {
|
||||
params.exposureValues[i] = exposures[i];
|
||||
}
|
||||
|
||||
void* paramsData;
|
||||
vkMapMemory(device, paramsBufferMemory, 0, sizeof(HdrMergeParams), 0, ¶msData);
|
||||
std::memcpy(paramsData, ¶ms, sizeof(HdrMergeParams));
|
||||
vkUnmapMemory(device, paramsBufferMemory);
|
||||
|
||||
// Record and submit command buffer
|
||||
VkCommandBufferBeginInfo beginInfo{};
|
||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
|
||||
vkBeginCommandBuffer(commandBuffer, &beginInfo);
|
||||
|
||||
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline);
|
||||
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
|
||||
|
||||
uint32_t groupCountX = (width + 15) / 16;
|
||||
uint32_t groupCountY = (height + 15) / 16;
|
||||
vkCmdDispatch(commandBuffer, groupCountX, groupCountY, 1);
|
||||
|
||||
vkEndCommandBuffer(commandBuffer);
|
||||
|
||||
VkSubmitInfo submitInfo{};
|
||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &commandBuffer;
|
||||
|
||||
vkQueueSubmit(computeQueue, 1, &submitInfo, VK_NULL_HANDLE);
|
||||
vkQueueWaitIdle(computeQueue);
|
||||
|
||||
// Debug input values - print first few pixels
|
||||
#if 0
|
||||
if (width > 0 && height > 0) {
|
||||
for (size_t img = 0; img < std::min(images.size(), size_t(2)); img++) {
|
||||
ALOGD("Image %zu first pixel: R=%.3f G=%.3f B=%.3f",
|
||||
img,
|
||||
images[img][0], // R
|
||||
images[img][1], // G
|
||||
images[img][2]); // B
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Download results
|
||||
outputData.resize(pixelCount * 3); // We'll extract RGB and ignore alpha
|
||||
|
||||
void* outputMappedMemory;
|
||||
vkMapMemory(device, outputBufferMemory, 0, outputBufferSize, 0, &outputMappedMemory);
|
||||
|
||||
float* outputFloats = static_cast<float*>(outputMappedMemory);
|
||||
|
||||
// Debug first few pixels of output
|
||||
#if 0
|
||||
ALOGD("Output first pixel: R=%.3f G=%.3f B=%.3f A=%.3f",
|
||||
outputFloats[0], outputFloats[1], outputFloats[2], outputFloats[3]);
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i < pixelCount; i++) {
|
||||
outputData[i * 3 + 0] = outputFloats[i * 4 + 0]; // R
|
||||
outputData[i * 3 + 1] = outputFloats[i * 4 + 1]; // G
|
||||
outputData[i * 3 + 2] = outputFloats[i * 4 + 2]; // B
|
||||
// Ignore alpha
|
||||
}
|
||||
|
||||
vkUnmapMemory(device, outputBufferMemory);
|
||||
}
|
||||
|
||||
bool VulkanHdrGenerator::generateHdr(
|
||||
const std::vector<std::string>& bmpFiles,
|
||||
const std::string& outputFile,
|
||||
const std::vector<float>& exposureValues,
|
||||
int32_t tileWidth,
|
||||
int32_t tileHeight) {
|
||||
|
||||
if (bmpFiles.empty()) {
|
||||
std::cerr << "No input BMP files specified" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check all images are the same dimensions
|
||||
std::vector<BmpInfo> bmpInfos(bmpFiles.size());
|
||||
for (size_t i = 0; i < bmpFiles.size(); i++) {
|
||||
bmpInfos[i] = BmpLoader::readBmpInfo(bmpFiles[i]);
|
||||
|
||||
if (i > 0) {
|
||||
if (bmpInfos[i].width != bmpInfos[0].width ||
|
||||
bmpInfos[i].height != bmpInfos[0].height) {
|
||||
std::cerr << "All BMP files must have the same dimensions" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t imageWidth = bmpInfos[0].width;
|
||||
int32_t imageHeight = bmpInfos[0].height;
|
||||
|
||||
// Adjust tile dimensions if necessary
|
||||
tileWidth = std::min(tileWidth, imageWidth);
|
||||
tileHeight = std::min(tileHeight, imageHeight);
|
||||
|
||||
// Output HDR data
|
||||
std::vector<float> outputHdrData(imageWidth * imageHeight * 3);
|
||||
|
||||
// Process image in tiles
|
||||
for (int32_t y = 0; y < imageHeight; y += tileHeight) {
|
||||
#ifndef NDEBUG
|
||||
ALOGI("Processing tile at Y=%d", y);
|
||||
#endif
|
||||
for (int32_t x = 0; x < imageWidth; x += tileWidth) {
|
||||
int32_t currentTileWidth = std::min(tileWidth, imageWidth - x);
|
||||
int32_t currentTileHeight = std::min(tileHeight, imageHeight - y);
|
||||
|
||||
// Load tile from each BMP
|
||||
std::vector<std::vector<float>> tileData(bmpFiles.size());
|
||||
for (size_t i = 0; i < bmpFiles.size(); i++) {
|
||||
tileData[i] = BmpLoader::readBmpRegionAsFloat(
|
||||
bmpFiles[i], bmpInfos[i], x, y, currentTileWidth, currentTileHeight);
|
||||
}
|
||||
|
||||
// Process tile with Vulkan
|
||||
std::vector<float> tileOutput;
|
||||
processImageBatch(tileData, tileOutput, currentTileWidth, currentTileHeight, exposureValues);
|
||||
|
||||
// Copy tile data to output HDR
|
||||
for (int32_t tileY = 0; tileY < currentTileHeight; tileY++) {
|
||||
for (int32_t tileX = 0; tileX < currentTileWidth; tileX++) {
|
||||
int32_t imagePixelIndex = ((y + tileY) * imageWidth + (x + tileX)) * 3;
|
||||
int32_t tilePixelIndex = (tileY * currentTileWidth + tileX) * 3;
|
||||
|
||||
outputHdrData[imagePixelIndex + 0] = tileOutput[tilePixelIndex + 0];
|
||||
outputHdrData[imagePixelIndex + 1] = tileOutput[tilePixelIndex + 1];
|
||||
outputHdrData[imagePixelIndex + 2] = tileOutput[tilePixelIndex + 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Write output HDR
|
||||
return HdrWriter::writeRGB(outputFile, outputHdrData, (int)imageWidth, (int)imageHeight, HdrWriter::Format::BMP);
|
||||
}
|
||||
|
||||
uint32_t VulkanHdrGenerator::findComputeQueueFamily(VkPhysicalDevice device) {
|
||||
uint32_t queueFamilyCount = 0;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
|
||||
|
||||
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
|
||||
|
||||
for (uint32_t i = 0; i < queueFamilyCount; i++) {
|
||||
if (queueFamilies[i].queueFlags & VK_QUEUE_COMPUTE_BIT) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("Failed to find a compute queue family");
|
||||
}
|
||||
|
||||
uint32_t VulkanHdrGenerator::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
|
||||
VkPhysicalDeviceMemoryProperties memProperties;
|
||||
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
|
||||
|
||||
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
|
||||
if ((typeFilter & (1 << i)) &&
|
||||
(memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("Failed to find suitable memory type");
|
||||
}
|
||||
|
||||
VkShaderModule VulkanHdrGenerator::createShaderModule(const std::vector<uint8_t>& code) {
|
||||
VkShaderModuleCreateInfo createInfo{};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
createInfo.codeSize = code.size();
|
||||
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
|
||||
|
||||
VkShaderModule shaderModule;
|
||||
if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create shader module");
|
||||
}
|
||||
|
||||
return shaderModule;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> VulkanHdrGenerator::readFile(const std::string& filename) {
|
||||
std::ifstream file(filename, std::ios::ate | std::ios::binary);
|
||||
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Failed to open file: " + filename);
|
||||
}
|
||||
|
||||
size_t fileSize = static_cast<size_t>(file.tellg());
|
||||
std::vector<uint8_t> buffer(fileSize);
|
||||
|
||||
file.seekg(0);
|
||||
file.read(reinterpret_cast<char*>(buffer.data()), fileSize);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void VulkanHdrGenerator::cleanup() {
|
||||
if (device != VK_NULL_HANDLE) {
|
||||
vkDeviceWaitIdle(device);
|
||||
|
||||
// Destroy buffers if they were created
|
||||
if (inputBuffer != VK_NULL_HANDLE) {
|
||||
vkDestroyBuffer(device, inputBuffer, nullptr);
|
||||
inputBuffer = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (inputBufferMemory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(device, inputBufferMemory, nullptr);
|
||||
inputBufferMemory = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (outputBuffer != VK_NULL_HANDLE) {
|
||||
vkDestroyBuffer(device, outputBuffer, nullptr);
|
||||
outputBuffer = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (outputBufferMemory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(device, outputBufferMemory, nullptr);
|
||||
outputBufferMemory = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (paramsBuffer != VK_NULL_HANDLE) {
|
||||
vkDestroyBuffer(device, paramsBuffer, nullptr);
|
||||
paramsBuffer = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (paramsBufferMemory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(device, paramsBufferMemory, nullptr);
|
||||
paramsBufferMemory = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// Destroy other resources
|
||||
if (descriptorPool != VK_NULL_HANDLE) {
|
||||
vkDestroyDescriptorPool(device, descriptorPool, nullptr);
|
||||
descriptorPool = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (descriptorSetLayout != VK_NULL_HANDLE) {
|
||||
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
|
||||
descriptorSetLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (computePipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, computePipeline, nullptr);
|
||||
computePipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (pipelineLayout != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
|
||||
pipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (commandPool != VK_NULL_HANDLE) {
|
||||
vkDestroyCommandPool(device, commandPool, nullptr);
|
||||
commandPool = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
vkDestroyDevice(device, nullptr);
|
||||
device = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (instance != VK_NULL_HANDLE) {
|
||||
vkDestroyInstance(instance, nullptr);
|
||||
instance = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,76 @@
|
||||
//
|
||||
// Created by Matthew on 2025/3/22.
|
||||
//
|
||||
|
||||
#ifndef MPPREVIEW_VULKAN_HDR_GENERATOR_H
|
||||
#define MPPREVIEW_VULKAN_HDR_GENERATOR_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <vulkan/vulkan.h>
|
||||
#include "BmpLoader.h"
|
||||
|
||||
struct HdrMergeParams {
|
||||
uint32_t imageCount;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
float exposureValues[16]; // Support up to 16 images
|
||||
};
|
||||
|
||||
class VulkanHdrGenerator {
|
||||
private:
|
||||
VkInstance instance;
|
||||
VkPhysicalDevice physicalDevice;
|
||||
VkDevice device;
|
||||
VkQueue computeQueue;
|
||||
VkCommandPool commandPool;
|
||||
VkDescriptorPool descriptorPool;
|
||||
VkCommandBuffer commandBuffer;
|
||||
|
||||
VkBuffer stagingBuffer;
|
||||
VkDeviceMemory stagingBufferMemory;
|
||||
|
||||
VkBuffer inputBuffer = nullptr;
|
||||
VkDeviceMemory inputBufferMemory;
|
||||
VkBuffer outputBuffer;
|
||||
VkDeviceMemory outputBufferMemory;
|
||||
VkBuffer paramsBuffer;
|
||||
VkDeviceMemory paramsBufferMemory;
|
||||
|
||||
VkShaderModule computeShaderModule;
|
||||
VkPipeline computePipeline;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
VkDescriptorSetLayout descriptorSetLayout;
|
||||
VkDescriptorSet descriptorSet;
|
||||
|
||||
uint32_t computeQueueFamilyIndex;
|
||||
|
||||
std::string compFilePath;
|
||||
|
||||
void setupVulkan();
|
||||
void createComputeResources();
|
||||
void createBuffers(VkDeviceSize inputSize, VkDeviceSize outputSize);
|
||||
void createDescriptorSet();
|
||||
uint32_t findComputeQueueFamily(VkPhysicalDevice device);
|
||||
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties);
|
||||
void processImageBatch(const std::vector<std::vector<float>>& images,
|
||||
std::vector<float>& outputData,
|
||||
int32_t width, int32_t height,
|
||||
const std::vector<float>& exposures);
|
||||
|
||||
VkShaderModule createShaderModule(const std::vector<uint8_t>& code);
|
||||
std::vector<uint8_t> readFile(const std::string& filename);
|
||||
void cleanup();
|
||||
|
||||
public:
|
||||
VulkanHdrGenerator(const std::string& compFile);
|
||||
~VulkanHdrGenerator();
|
||||
|
||||
// Generate HDR from multiple BMP files with tile-based processing
|
||||
bool generateHdr(const std::vector<std::string>& bmpFiles,
|
||||
const std::string& outputFile,
|
||||
const std::vector<float>& exposureValues,
|
||||
int32_t tileWidth = 256, int32_t tileHeight = 256);
|
||||
};
|
||||
|
||||
#endif //MPPREVIEW_VULKAN_HDR_GENERATOR_H
|
Loading…
Reference in New Issue