/** * @file llimagej2cnv.cpp * @brief This is an implementation of JPEG2000 encode/decode using nvJPEG2000. */ #include "linden_common.h" #include "llimagej2cnv.h" #include #include #include // 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 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(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; }