phoenix-firestorm/indra/llimagej2cnv/llimagej2cnv.cpp

314 lines
9.0 KiB
C++

/**
* @file llimagej2cnv.cpp
* @brief This is an implementation of JPEG2000 encode/decode using nvJPEG2000.
*/
#include "linden_common.h"
#include "llimagej2cnv.h"
#include <cuda_runtime_api.h>
#include <nvjpeg2k.h>
#include <nvjpeg2k_version.h>
// Factory function: see declaration in llimagej2c.cpp
LLImageJ2CImpl* fallbackCreateLLImageJ2CImpl() {
return new LLImageJ2CNV();
}
#pragma region NVIMAGE
void NvImage::clear() {
m_imageDevice.num_components = 0;
m_imageDevice.pixel_data = nullptr;
m_imageDevice.pitch_in_bytes = nullptr;
m_imageDevice.pixel_type = NVJPEG2K_UINT8;
m_imageHost.num_components = 0;
m_imageHost.pixel_data = nullptr;
m_imageHost.pitch_in_bytes = nullptr;
m_imageHost.pixel_type = NVJPEG2K_UINT8;
m_capacity = 0;
}
nvjpeg2kColorSpace_t NvImage::getColorSpace() {
return m_colorSpace;
}
nvjpeg2kImage_t& NvImage::getImageHost() {
return m_imageHost;
}
nvjpeg2kImage_t& NvImage::getImageDevice() {
return m_imageDevice;
}
nvjpeg2kImageInfo_t& NvImage::getImageInfo() {
return m_info;
}
nvjpeg2kImageComponentInfo_t* NvImage::getComponentInfo() {
return m_compInfo.data();
}
NvImage::NvImage() {
clear();
}
bool NvImage::init(nvjpeg2kImageInfo_t& imageInfo, nvjpeg2kImageComponentInfo_t* compInfo, nvjpeg2kColorSpace_t colorSpace) {
memcpy(&m_info, &imageInfo, sizeof(imageInfo));
m_compInfo.resize(m_info.num_components);
m_colorSpace = colorSpace;
for (U32 c = 0; c < m_info.num_components; c++) {
memcpy(&m_compInfo[c], &compInfo[c], sizeof(m_compInfo[c]));
}
if (m_info.num_components > m_capacity) {
m_pixelDataDevice.resize(m_info.num_components, nullptr);
m_pitchBytesDevice.resize(m_info.num_components, 0);
m_pixelDataHost.resize(m_info.num_components, nullptr);
m_pitchBytesHost.resize(m_info.num_components, 0);
m_pixelDataSize.resize(m_info.num_components, 0);
m_capacity = m_info.num_components;
}
m_imageDevice.pixel_data = m_pixelDataDevice.data();
m_imageDevice.pitch_in_bytes = m_pitchBytesDevice.data();
m_imageHost.pixel_data = m_pixelDataHost.data();
m_imageHost.pitch_in_bytes = m_pitchBytesHost.data();
if (m_compInfo[0].precision <= 8) {
m_imageDevice.pixel_type = NVJPEG2K_UINT8;
m_imageHost.pixel_type = NVJPEG2K_UINT8;
} else if (m_compInfo[0].precision > 8 && m_compInfo[0].precision <= 16) {
m_imageDevice.pixel_type = NVJPEG2K_UINT16;
m_imageHost.pixel_type = NVJPEG2K_UINT16;
} else {
LL_ERRS() << "nvJPEG2000 failed to initialise image: precision too large" << LL_ENDL;
return false;
}
m_imageDevice.num_components = m_info.num_components;
m_imageHost.num_components = m_info.num_components;
U8 bytesPerElement = (m_imageDevice.pixel_type == NVJPEG2K_UINT16) ? 2 : 1;
for (U32 c = 0; c < m_info.num_components; c++) {
m_imageDevice.pitch_in_bytes[c] =
m_imageHost.pitch_in_bytes[c] = m_compInfo[c].component_width * bytesPerElement;
size_t compSize = m_compInfo[c].component_height * m_imageDevice.pitch_in_bytes[c];
if (compSize > m_pixelDataSize[c]) {
if (m_imageDevice.pixel_data[c]) {
CHECK_CUDA(cudaFree(m_imageDevice.pixel_data[c]));
}
if (m_imageHost.pixel_data[c]) {
free(m_imageHost.pixel_data[c]);
}
m_pixelDataSize[c] = compSize;
CHECK_CUDA(cudaMalloc(&m_imageDevice.pixel_data[c], compSize));
m_imageHost.pixel_data[c] = malloc(compSize);
}
}
return true;
}
bool NvImage::cleanup() {
for (U32 c = 0; c < m_capacity; c++) {
if (m_imageDevice.pixel_data[c]) {
CHECK_CUDA(cudaFree(m_pixelDataDevice[c]));
m_pixelDataDevice[c] = nullptr;
}
if (m_imageHost.pixel_data[c]) {
free(m_pixelDataHost[c]);
m_pixelDataHost[c] = nullptr;
}
m_pixelDataSize[c] = 0;
}
return true;
}
bool NvImage::copyToDevice() {
U8 bytesPerElement = m_imageDevice.pixel_type == NVJPEG2K_UINT16 ? 2 : 1;
for (U32 c = 0; c < m_imageDevice.num_components; c++) {
CHECK_CUDA(cudaMemcpy2D(
m_imageDevice.pixel_data[c],
m_imageDevice.pitch_in_bytes[c],
m_imageHost.pixel_data[c],
m_imageHost.pitch_in_bytes[c],
m_compInfo[c].component_width * bytesPerElement,
m_compInfo[c].component_height, cudaMemcpyHostToDevice));
CHECK_CUDA(cudaDeviceSynchronize());
}
return true;
}
#pragma endregion NVIMAGE
#pragma region ENCODER
bool LLNVJ2KEncoder::createEncoder() {
cleanup();
CHECK_NVJ2K(nvjpeg2kEncoderCreateSimple(&m_encoderHandle));
CHECK_NVJ2K(nvjpeg2kEncodeStateCreate(m_encoderHandle, &m_encoderState));
CHECK_NVJ2K(nvjpeg2kEncodeParamsCreate(&m_encoderParams));
m_encoderCreated = true;
}
void LLNVJ2KEncoder::cleanup() {
if (m_encoderParams != nullptr) {
CHECK_NVJ2K_NORETURN(nvjpeg2kEncodeParamsDestroy(m_encoderParams));
}
m_encoderParams = nullptr;
if (m_encoderState != nullptr) {
CHECK_NVJ2K_NORETURN(nvjpeg2kEncodeStateDestroy(m_encoderState));
}
m_encoderState = nullptr;
if (m_encoderHandle != nullptr) {
CHECK_NVJ2K_NORETURN(nvjpeg2kEncoderDestroy(m_encoderHandle));
}
m_encoderHandle = nullptr;
m_encoderCreated = false;
}
LLNVJ2KEncoder::~LLNVJ2KEncoder() {
cleanup();
}
bool LLNVJ2KEncoder::encode(const LLImageRaw &rawImageIn, LLImageJ2C &compressedImageOut) {
LLImageDataSharedLock lockIn(&rawImageIn);
LLImageDataLock lockOut(&compressedImageOut);
if (!m_encoderCreated) {
if (!createEncoder()) {
return false;
}
}
if (!setImage(rawImageIn)) {
return false;
}
nvjpeg2kEncodeConfig_t encodeCfg;
encodeCfg.stream_type = NVJPEG2K_STREAM_J2K;
encodeCfg.color_space = m_nvImage.getColorSpace();
encodeCfg.rsiz = 0;
encodeCfg.image_width = m_nvImage.getImageInfo().image_width;
encodeCfg.image_height = m_nvImage.getImageInfo().image_height;
encodeCfg.prog_order = NVJPEG2K_RLCP;
}
bool LLNVJ2KEncoder::setImage(const LLImageRaw& raw) {
S8 numcomps = raw.getComponents();
U16 width = raw.getWidth();
U16 height = raw.getHeight();
nvjpeg2kColorSpace_t colorSpace = NVJPEG2K_COLORSPACE_SRGB;
nvjpeg2kImageInfo_t imageInfo;
std::vector<nvjpeg2kImageComponentInfo_t> compInfo(numcomps);
imageInfo.num_components = numcomps;
imageInfo.image_width = width;
imageInfo.image_height = height;
for (auto &comp : compInfo) {
comp.component_width = width;
comp.component_height = height;
comp.precision = 8;
comp.sgn = 0;
}
if (!m_nvImage.init(imageInfo, compInfo.data(), colorSpace)) {
return false;
}
const U8 *pSrcData = raw.getData();
auto &dstImage = m_nvImage.getImageHost();
// ????????????????
S32 i = 0;
for (S32 y = height - 1; y >= 0; y--) {
for (S32 x = 0; x < width; x++) {
const U8 *pixel = pSrcData + (y * width + x) * numcomps;
for (S32 c = 0; c < numcomps; c++) {
// Absolute fuckery ahead, viewer discretion advised
U8 *pixBuf = static_cast<U8*>(dstImage.pixel_data[c]);
pixBuf[i] = *pixel;
pixel++;
}
i++;
}
}
}
#pragma endregion ENCODER
std::string LLImageJ2CNV::getEngineInfo() const {
cudaDeviceProp props;
int dev = 0;
cudaGetDevice(&dev);
cudaGetDeviceProperties(&props, dev);
return llformat("nvJPEG2000, Version: %i.%i.%i.%i, GPU: %s, CC: %i.%i",
NVJPEG2K_VER_MAJOR, NVJPEG2K_VER_MINOR, NVJPEG2K_VER_PATCH, NVJPEG2K_VER_BUILD,
props.name, props.major, props.minor);
}
LLImageJ2CNV::LLImageJ2CNV() : LLImageJ2CImpl() {}
LLImageJ2CNV::~LLImageJ2CNV() {}
bool LLImageJ2CNV::initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int blocks_size = -1, int precincts_size = -1, int levels = 0) {
// TODO: impl
return false;
}
bool LLImageJ2CNV::initDecode(LLImageJ2C &base, LLImageRaw &raw_image, int discard_level = -1, int* region = NULL) {
// TODO: impl
return false;
}
bool LLImageJ2CNV::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time=0.0, bool reversible = false) {
if (raw_image.isBufferInvalid()) {
base.setLastError("Invalid input, no buffer");
return false;
}
LLNVJ2KEncoder encoder;
if (!encoder.createEncoder()) {
LL_WARNS() << "Failed to create nvJPEG2000 encoder." << LL_ENDL;
return false;
}
bool encoded = encoder.encode(raw_image, base);
if (!encoded) {
LL_WARNS() << "nvJPEG2000 encoding was unsuccessful, returning false." << LL_ENDL;
}
return encoded;
}
bool LLImageJ2CNV::decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count) {
// TODO: impl
return false;
}
bool LLImageJ2CNV::getMetadata(LLImageJ2C &base) {
// TODO: impl
return false;
}