Skip to content

feat: implement BasisFile #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 208 additions & 0 deletions cpp/BasisFile.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
#include "BasisFile.h"
#include <vector>
#include <stdexcept>
#include <cstring>

#define BASIS_MAGIC 0xDEADBEE1

using namespace basist;
using namespace basisu;

namespace facebook::react {

BasisFile::BasisFile(jsi::Runtime &rt, const jsi::ArrayBuffer& buffer) : m_file([&]() {
size_t byteLength = buffer.size(rt);
return basisu::vector<uint8_t>(byteLength);
}()), m_magic(0) {
size_t byteLength = buffer.size(rt);
m_file.resize(byteLength);
std::memcpy(m_file.data(), buffer.data(rt), byteLength);

if (!m_transcoder.validate_header(m_file.data(), m_file.size())) {
throw std::runtime_error("Invalid Basis file header");
}

// Initialized after validation
m_magic = BASIS_MAGIC;
}

uint32_t BasisFile::getHasAlpha() {
if (m_magic != BASIS_MAGIC) return 0;

basist::basisu_image_level_info li;
if (!m_transcoder.get_image_level_info(m_file.data(), m_file.size(), li, 0, 0))
return 0;

return li.m_alpha_flag;
}

uint32_t BasisFile::getNumImages() {
if (m_magic != BASIS_MAGIC) return 0;
return m_transcoder.get_total_images(m_file.data(), m_file.size());
}

uint32_t BasisFile::getNumLevels(uint32_t image_index)
{
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return 0;

basisu_image_info ii;
if (!m_transcoder.get_image_info(m_file.data(), m_file.size(), ii, image_index))
return 0;

return ii.m_total_levels;
}

uint32_t BasisFile::getImageTranscodedSizeInBytes(uint32_t image_index, uint32_t level_index, uint32_t format) {
if (m_magic != BASIS_MAGIC) return 0;
if (format >= static_cast<uint32_t>(basist::transcoder_texture_format::cTFTotalTextureFormats)) return 0;

uint32_t orig_width, orig_height, total_blocks;
if (!m_transcoder.get_image_level_desc(m_file.data(), m_file.size(), image_index, level_index, orig_width, orig_height, total_blocks))
return 0;

const basist::transcoder_texture_format transcoder_format = static_cast<basist::transcoder_texture_format>(format);

if (basist::basis_transcoder_format_is_uncompressed(transcoder_format)) {
const uint32_t bytes_per_pixel = basist::basis_get_uncompressed_bytes_per_pixel(transcoder_format);
return orig_width * orig_height * bytes_per_pixel;
} else {
const uint32_t bytes_per_block = basist::basis_get_bytes_per_block_or_pixel(transcoder_format);

if (transcoder_format == basist::transcoder_texture_format::cTFPVRTC1_4_RGB ||
transcoder_format == basist::transcoder_texture_format::cTFPVRTC1_4_RGBA) {
const uint32_t width = (orig_width + 3) & ~3;
const uint32_t height = (orig_height + 3) & ~3;
return (std::max(8U, width) * std::max(8U, height) * 4 + 7) / 8;
}

return total_blocks * bytes_per_block;
}
}

bool BasisFile::isUASTC() {
if (m_magic != BASIS_MAGIC) return false;
return m_transcoder.get_tex_format(m_file.data(), m_file.size()) == basist::basis_tex_format::cUASTC4x4;
}

bool BasisFile::isHDR() {
if (m_magic != BASIS_MAGIC) return false;
return m_transcoder.get_tex_format(m_file.data(), m_file.size()) == basist::basis_tex_format::cUASTC_HDR_4x4;
}

uint32_t BasisFile::startTranscoding() {
if (m_magic != BASIS_MAGIC) return 0;
return m_transcoder.start_transcoding(m_file.data(), m_file.size());
}

uint32_t BasisFile::transcodeImage(jsi::Runtime& rt,
jsi::Object& destination,
uint32_t image_index,
uint32_t level_index,
uint32_t format,
uint32_t unused,
uint32_t get_alpha_for_opaque_formats) {

assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return 0;

if (format >= (int)transcoder_texture_format::cTFTotalTextureFormats)
return 0;

const transcoder_texture_format transcoder_format = static_cast<transcoder_texture_format>(format);

uint32_t orig_width, orig_height, total_blocks;
if (!m_transcoder.get_image_level_desc(m_file.data(), m_file.size(), image_index, level_index, orig_width, orig_height, total_blocks))
return 0;

basisu::vector<uint8_t> dst_data;

uint32_t flags = get_alpha_for_opaque_formats ? cDecodeFlagsTranscodeAlphaDataToOpaqueFormats : 0;

uint32_t status;

if (basis_transcoder_format_is_uncompressed(transcoder_format))
{
const uint32_t bytes_per_pixel = basis_get_uncompressed_bytes_per_pixel(transcoder_format);
const uint32_t bytes_per_line = orig_width * bytes_per_pixel;
const uint32_t bytes_per_slice = bytes_per_line * orig_height;

dst_data.resize(bytes_per_slice);

status = m_transcoder.transcode_image_level(
m_file.data(), m_file.size(), image_index, level_index,
dst_data.data(), orig_width * orig_height,
transcoder_format,
flags,
orig_width,
nullptr,
orig_height);
}
else
{
uint32_t bytes_per_block = basis_get_bytes_per_block_or_pixel(transcoder_format);

uint32_t required_size = total_blocks * bytes_per_block;

if (transcoder_format == transcoder_texture_format::cTFPVRTC1_4_RGB || transcoder_format == transcoder_texture_format::cTFPVRTC1_4_RGBA)
{
// For PVRTC1, Basis only writes (or requires) total_blocks * bytes_per_block. But GL requires extra padding for very small textures:
// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt
// The transcoder will clear the extra bytes followed the used blocks to 0.
const uint32_t width = (orig_width + 3) & ~3;
const uint32_t height = (orig_height + 3) & ~3;
required_size = (std::max(8U, width) * std::max(8U, height) * 4 + 7) / 8;
assert(required_size >= total_blocks * bytes_per_block);
}

dst_data.resize(required_size);

status = m_transcoder.transcode_image_level(
m_file.data(), m_file.size(), image_index, level_index,
dst_data.data(), dst_data.size() / bytes_per_block,
static_cast<basist::transcoder_texture_format>(format),
flags);
}

auto arrayBuffer = destination.getArrayBuffer(rt);

auto outputBuffer = jsi::ArrayBuffer(std::move(arrayBuffer));
memcpy(outputBuffer.data(rt), dst_data.data(), dst_data.size());
destination.setProperty(rt, jsi::PropNameID::forAscii(rt, "buffer"), outputBuffer);

return status;
}

uint32_t BasisFile::getImageHeight(uint32_t image_index, uint32_t level_index) {
if (m_magic != BASIS_MAGIC) return 0;

uint32_t orig_width, orig_height, total_blocks;
if (!m_transcoder.get_image_level_desc(m_file.data(), m_file.size(), image_index, level_index, orig_width, orig_height, total_blocks))
return 0;

return orig_height;
}

uint32_t BasisFile::getImageWidth(uint32_t image_index, uint32_t level_index) {
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return 0;

uint32_t orig_width, orig_height, total_blocks;
if (!m_transcoder.get_image_level_desc(m_file.data(), m_file.size(), image_index, level_index, orig_width, orig_height, total_blocks))
return 0;

return orig_width;
}

void BasisFile::close() {
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return;

m_file.clear();
}

} // namespace facebook::react
41 changes: 41 additions & 0 deletions cpp/BasisFile.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include <jsi/jsi.h>
#include "rn_basis_universal/transcoder/basisu_transcoder.h"
#include <vector>
#include <cstdint>

using namespace basist;
using namespace basisu;

namespace facebook::react {

class BasisFile : public jsi::NativeState {
public:
BasisFile(jsi::Runtime &rt, const jsi::ArrayBuffer& buffer);

void close();
uint32_t getHasAlpha();
uint32_t getNumImages();
uint32_t getNumLevels(uint32_t image_index);
uint32_t getImageWidth(uint32_t image_index, uint32_t level_index);
uint32_t getImageHeight(uint32_t image_index, uint32_t level_index);
uint32_t getImageTranscodedSizeInBytes(uint32_t image_index, uint32_t level_index, uint32_t format);
bool isUASTC();
bool isHDR();
uint32_t startTranscoding();
uint32_t transcodeImage(jsi::Runtime& rt,
jsi::Object& destination,
uint32_t image_index,
uint32_t level_index,
uint32_t format,
uint32_t unused,
uint32_t get_alpha_for_opaque_formats);

private:
int m_magic;
basisu_transcoder m_transcoder;
basisu::vector<uint8_t> m_file;
};

}
92 changes: 90 additions & 2 deletions cpp/react-native-basis-universal.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "react-native-basis-universal.h"
#include "KTX2File.h"
#include <valarray>
#include "BasisFile.h"

#define DEFINE_BASIS_ENCODER_PARAMS_SETTER(func_name, param_name, param_type) \
void ReactNativeBasisUniversal::func_name(jsi::Runtime &rt, jsi::Object handle, param_type flag) { \
Expand Down Expand Up @@ -49,6 +49,15 @@ std::shared_ptr<KTX2File> tryGetKTX2Handle(jsi::Runtime& rt, jsi::Object& kt2xHa
return ktx2file;
}

std::shared_ptr<BasisFile> tryGetBasisFileHandle(jsi::Runtime& rt, jsi::Object& basisFileHandle) {
if (!basisFileHandle.hasNativeState(rt)) {
return nullptr;
}

auto basisFile = std::dynamic_pointer_cast<BasisFile>(basisFileHandle.getNativeState(rt));
return basisFile;
}

ReactNativeBasisUniversal::ReactNativeBasisUniversal(std::shared_ptr<CallInvoker> jsInvoker)
: NativeBasisUniversalCxxSpecJSI(jsInvoker) {}

Expand Down Expand Up @@ -396,7 +405,6 @@ int ReactNativeBasisUniversal::getImageTranscodedSizeInBytes(jsi::Runtime &rt, j
return ktx2Handle->getImageTranscodedSizeInBytes(levelIndex, layerIndex, faceIndex, format);
}

// TODO: Used in IREngine
int ReactNativeBasisUniversal::transcodeImage(jsi::Runtime &rt, jsi::Object handle, jsi::Object dst, int levelIndex, int layerIndex, int faceIndex, int format, int getAlphaForOpaqueFormats, int channel0, int channel1) {
auto ktx2Handle = tryGetKTX2Handle(rt, handle);
return ktx2Handle->transcodeImage(rt,
Expand All @@ -421,4 +429,84 @@ int ReactNativeBasisUniversal::getKeyValue(jsi::Runtime &rt, jsi::Object handle,
return 0;
}

// Basis File

jsi::Object ReactNativeBasisUniversal::createBasisFile(jsi::Runtime &rt, jsi::Object data) {
jsi::Object basisObject{rt};
basisObject.setNativeState(rt, std::make_shared<BasisFile>(rt, data.getArrayBuffer(rt)));
return basisObject;
}

void ReactNativeBasisUniversal::closeBasisFile(jsi::Runtime &rt, jsi::Object handle) {
auto fileHandle = tryGetBasisFileHandle(rt, handle);
fileHandle->close();
}

bool ReactNativeBasisUniversal::getHasAlphaBasisFile(jsi::Runtime &rt, jsi::Object handle) {
auto fileHandle = tryGetBasisFileHandle(rt, handle);
return fileHandle->getHasAlpha();
}

bool ReactNativeBasisUniversal::isUASTCBasisFile(jsi::Runtime &rt, jsi::Object handle) {
auto fileHandle = tryGetBasisFileHandle(rt, handle);
return fileHandle->isUASTC();
}

bool ReactNativeBasisUniversal::isHDRBasisFile(jsi::Runtime &rt, jsi::Object handle) {
auto fileHandle = tryGetBasisFileHandle(rt, handle);
return fileHandle->isHDR();
}

int ReactNativeBasisUniversal::getNumImagesBasisFile(jsi::Runtime &rt, jsi::Object handle) {
auto fileHandle = tryGetBasisFileHandle(rt, handle);
return fileHandle->getNumImages();
}

int ReactNativeBasisUniversal::getNumLevels(jsi::Runtime &rt, jsi::Object handle, int imageIndex) {
auto fileHandle = tryGetBasisFileHandle(rt, handle);
return fileHandle->getNumLevels(imageIndex);
}

int ReactNativeBasisUniversal::getImageWidthBasisFile(jsi::Runtime &rt, jsi::Object handle, int imageIndex, int levelIndex) {
auto fileHandle = tryGetBasisFileHandle(rt, handle);
return fileHandle->getImageWidth(imageIndex, levelIndex);
}

int ReactNativeBasisUniversal::getImageHeightBasisFile(jsi::Runtime &rt, jsi::Object handle, int imageIndex, int levelIndex) {
auto fileHandle = tryGetBasisFileHandle(rt, handle);
return fileHandle->getImageHeight(imageIndex, levelIndex);
}

int ReactNativeBasisUniversal::getImageTranscodedSizeInBytesBasisFile(jsi::Runtime &rt, jsi::Object handle, int imageIndex, int levelIndex, int format) {
auto fileHandle = tryGetBasisFileHandle(rt, handle);
return fileHandle->getImageTranscodedSizeInBytes(imageIndex, levelIndex, format);
}

bool ReactNativeBasisUniversal::startTranscodingBasisFile(jsi::Runtime &rt, jsi::Object handle) {
auto fileHandle = tryGetBasisFileHandle(rt, handle);
return fileHandle->startTranscoding();
}

bool ReactNativeBasisUniversal::transcodeImageBasisFile(jsi::Runtime &rt, jsi::Object handle, jsi::Object dst, int imageIndex, int levelIndex, int format, int unused, int getAlphaForOpaqueFormats) {
auto fileHandle = tryGetBasisFileHandle(rt, handle);
return fileHandle->transcodeImage(rt, dst, imageIndex, levelIndex, format, unused, getAlphaForOpaqueFormats);
}

jsi::Object ReactNativeBasisUniversal::getFileDescBasisFile(jsi::Runtime &rt, jsi::Object handle) {
// TODO: Implement getFileDescBasisFile (Not used in IREngine)
return jsi::Object(rt);
}

jsi::Object ReactNativeBasisUniversal::getImageDescBasisFile(jsi::Runtime &rt, jsi::Object handle, int imageIndex) {
// TODO: Implement getImageDescBasisFile (Not used in IREngine)
return jsi::Object(rt);
}

jsi::Object ReactNativeBasisUniversal::getImageLevelDescBasisFile(jsi::Runtime &rt, jsi::Object handle, int imageIndex, int levelIndex) {
// TODO: Implement getImageLevelDescBasisFile (Not used in IREngine)
return jsi::Object(rt);
}



}
17 changes: 16 additions & 1 deletion cpp/react-native-basis-universal.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,22 @@ class ReactNativeBasisUniversal : public NativeBasisUniversalCxxSpecJSI {
bool startTranscoding(jsi::Runtime &rt, jsi::Object handle) override;
int transcodeImage(jsi::Runtime &rt, jsi::Object handle, jsi::Object dst, int levelIndex, int layerIndex, int faceIndex, int format, int getAlphaForOpaqueFormats, int channel0, int channel1) override;


// Basis file
virtual jsi::Object createBasisFile(jsi::Runtime &rt, jsi::Object data) override;
void closeBasisFile(jsi::Runtime &rt, jsi::Object handle) override;
bool getHasAlphaBasisFile(jsi::Runtime &rt, jsi::Object handle) override;
bool isUASTCBasisFile(jsi::Runtime &rt, jsi::Object handle) override;
bool isHDRBasisFile(jsi::Runtime &rt, jsi::Object handle) override;
int getNumImagesBasisFile(jsi::Runtime &rt, jsi::Object handle) override;
int getNumLevels(jsi::Runtime &rt, jsi::Object handle, int imageIndex) override;
int getImageWidthBasisFile(jsi::Runtime &rt, jsi::Object handle, int imageIndex, int levelIndex) override;
int getImageHeightBasisFile(jsi::Runtime &rt, jsi::Object handle, int imageIndex, int levelIndex) override;
int getImageTranscodedSizeInBytesBasisFile(jsi::Runtime &rt, jsi::Object handle, int imageIndex, int levelIndex, int format) override;
bool startTranscodingBasisFile(jsi::Runtime &rt, jsi::Object handle) override;
bool transcodeImageBasisFile(jsi::Runtime &rt, jsi::Object handle, jsi::Object dst, int imageIndex, int levelIndex, int format, int unused, int getAlphaForOpaqueFormats) override;
jsi::Object getFileDescBasisFile(jsi::Runtime &rt, jsi::Object handle) override;
jsi::Object getImageDescBasisFile(jsi::Runtime &rt, jsi::Object handle, int imageIndex) override;
jsi::Object getImageLevelDescBasisFile(jsi::Runtime &rt, jsi::Object handle, int imageIndex, int levelIndex) override;

private:
bool basis_initialized_flag;
Expand Down
Loading
Loading