Skip to content

Commit

Permalink
Merge pull request #2 from callstack/feat/encode
Browse files Browse the repository at this point in the history
Feat/encode
  • Loading branch information
okwasniewski authored Oct 21, 2024
2 parents 2c72be6 + e5b3390 commit 8d64f21
Show file tree
Hide file tree
Showing 19 changed files with 909 additions and 193 deletions.
18 changes: 4 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ jobs:

build-android:
runs-on: ubuntu-latest
# Skip Android builds
if: false
env:
TURBO_CACHE_DIR: .turbo/android
steps:
Expand Down Expand Up @@ -133,22 +135,10 @@ jobs:
echo "turbo_cache_hit=1" >> $GITHUB_ENV
fi
- name: Cache cocoapods
if: env.turbo_cache_hit != 1
id: cocoapods-cache
uses: actions/cache@v3
with:
path: |
**/ios/Pods
key: ${{ runner.os }}-cocoapods-${{ hashFiles('example/ios/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-cocoapods-
- name: Install cocoapods
if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true'
run: |
cd example/ios
pod install
cd example
pod install --project-directory=ios
env:
NO_FLIPPER: 1

Expand Down
231 changes: 207 additions & 24 deletions cpp/react-native-basis-universal.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
#include "react-native-basis-universal.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) { \
auto encoder = tryGetBasisEncoder(rt, handle); \
encoder->m_params.param_name = flag; \
}


using namespace basist;
using namespace basisu;

Expand All @@ -14,26 +21,26 @@ std::shared_ptr<BasisEncoder> tryGetBasisEncoder(jsi::Runtime& rt, jsi::Object&
if (!basisEncoderObj.hasNativeState(rt)) {
return nullptr;
}

auto encoder = std::dynamic_pointer_cast<BasisEncoder>(basisEncoderObj.getNativeState(rt));
return encoder;
}

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

void ReactNativeBasisUniversal::initializeBasis(jsi::Runtime &rt)
{
if (basis_initialized_flag) {
return;
}

#if BASISU_SUPPORT_ENCODING
basisu_encoder_init();
#endif

basisu_transcoder_init();

basis_initialized_flag = true;
}

Expand All @@ -43,49 +50,225 @@ jsi::Object ReactNativeBasisUniversal::createBasisHandle(jsi::Runtime &rt) {
return basisObject;
}

void ReactNativeBasisUniversal::setDebug(jsi::Runtime &rt, jsi::Object handle, bool flag) {
int ReactNativeBasisUniversal::encode(jsi::Runtime &rt, jsi::Object handle, jsi::Object basisFileData) {
auto encoder = tryGetBasisEncoder(rt, handle);
encoder->m_params.m_debug = flag;
}

void ReactNativeBasisUniversal::setCreateKTX2File(jsi::Runtime &rt, jsi::Object handle, bool flag) {
auto encoder = tryGetBasisEncoder(rt, handle);
encoder->m_params.m_create_ktx2_file = flag;
if (!basisFileData.isArrayBuffer(rt)) {
throw jsi::JSError(rt, "Image Array needs to be ArrayBuffer");
}

auto arrayBuffer = basisFileData.getArrayBuffer(rt);

if (!basis_initialized_flag)
{
assert(0);
return 0;
}

job_pool jpool(6);

// Initialize the compression parameters structure. This is the same structure that the command line tool fills in.
basis_compressor_params &params = encoder->m_params;

params.m_pJob_pool = &jpool;

params.m_multithreading = true;

params.m_status_output = params.m_debug;

params.m_read_source_images = false;
params.m_write_output_basis_or_ktx2_files = false;

basis_compressor comp;

if (!comp.init(params))
{
return 0;
}

basis_compressor::error_code ec = comp.process();

if (ec != basis_compressor::cECSuccess)
{
// Something failed during compression.
return 0;
}

if (params.m_create_ktx2_file)
{
// Compression succeeded, so copy the .ktx2 file bytes to the caller's buffer.
auto output = comp.get_output_ktx2_file();

if (!output.data()) {
return 0;
}

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


// Return the file size of the .basis file in bytes.
return (uint32_t)comp.get_output_ktx2_file().size();
}
else
{
auto output = comp.get_output_basis_file();

if (!output.data()) {
return 0;
}

// Compression succeeded, so copy the .basis file bytes to the caller's buffer.
auto outputBuffer = jsi::ArrayBuffer(std::move(arrayBuffer));
memcpy(outputBuffer.data(rt), output.data(), output.size());
basisFileData.setProperty(rt, jsi::PropNameID::forAscii(rt, "buffer"), outputBuffer);

// Return the file size of the .basis file in bytes.
return (uint32_t)comp.get_output_basis_file().size();
}

return 0;
}

void ReactNativeBasisUniversal::setComputeStats(jsi::Runtime &rt, jsi::Object handle, bool flag) {
void ReactNativeBasisUniversal::setKTX2UASTCSupercompression(jsi::Runtime &rt, jsi::Object handle, bool flag) {
auto encoder = tryGetBasisEncoder(rt, handle);
encoder->m_params.m_compute_stats = flag;
encoder->m_params.m_ktx2_uastc_supercompression = flag ? basist::KTX2_SS_ZSTANDARD : basist::KTX2_SS_NONE;
}

void ReactNativeBasisUniversal::setUASTC(jsi::Runtime &rt, jsi::Object handle, bool flag) {
void ReactNativeBasisUniversal::setQualityLevel(jsi::Runtime &rt, jsi::Object handle, int qualityLevel) {
auto encoder = tryGetBasisEncoder(rt, handle);
encoder->m_params.m_uastc = flag;
assert(qualityLevel >= -1 && qualityLevel <= BASISU_QUALITY_MAX);
encoder->m_params.m_quality_level = qualityLevel;
}

void ReactNativeBasisUniversal::setKTX2UASTCSupercompression(jsi::Runtime &rt, jsi::Object handle, bool flag) {
bool ReactNativeBasisUniversal::setSliceSourceImage(jsi::Runtime &rt, jsi::Object handle, int sliceIndex, jsi::Object imageArray, int width, int height, bool isPng) {
if (!imageArray.isArrayBuffer(rt)) {
throw jsi::JSError(rt, "Image Array needs to be ArrayBuffer");
}
auto encoder = tryGetBasisEncoder(rt, handle);
encoder->m_params.m_ktx2_uastc_supercompression = flag ? basist::KTX2_SS_ZSTANDARD : basist::KTX2_SS_NONE;;
}

void ReactNativeBasisUniversal::setQualityLevel(jsi::Runtime &rt, jsi::Object handle, int qualityLevel) {

if (sliceIndex >= encoder->m_params.m_source_images.size())
encoder->m_params.m_source_images.resize(sliceIndex + 1);

auto arrayBuffer = imageArray.getArrayBuffer(rt);
auto data = arrayBuffer.data(rt);

image& src_img = encoder->m_params.m_source_images[sliceIndex];

if (isPng)
{
auto size = arrayBuffer.size(rt);
// It's a PNG file, so try and parse it.
if (!load_png(data, size, src_img, nullptr))
{
return false;
}

width = src_img.get_width();
height = src_img.get_height();
}
else
{
// It's a raw image, so check the buffer's size.
if (arrayBuffer.size(rt) != width * height * sizeof(uint32_t))
{
return false;
}

// Copy the raw image's data into our source image.
src_img.resize(width, height);
memcpy(src_img.get_ptr(), arrayBuffer.data(rt), width * height * sizeof(uint32_t));
}

return true;
}

void ReactNativeBasisUniversal::setSliceSourceImage(jsi::Runtime &rt, jsi::Object handle, int sliceIndex, jsi::Object imageArray, int width, int height, bool isPng) {
if (imageArray.isArrayBuffer(rt)) {
bool ReactNativeBasisUniversal::setSliceSourceImageHDR(jsi::Runtime &rt, jsi::Object handle, int sliceIndex, jsi::Object imageArray, int width, int height, int imgType, bool ldrSrgbToLinear) {
if (!imageArray.isArrayBuffer(rt)) {
throw jsi::JSError(rt, "Image Array needs to be ArrayBuffer");
}
auto encoder = tryGetBasisEncoder(rt, handle);

auto arrayBuffer = imageArray.getArrayBuffer(rt);
auto data = arrayBuffer.data(rt);

// Resize the source_images_hdr array if necessary
if (sliceIndex >= encoder->m_params.m_source_images_hdr.size())
encoder->m_params.m_source_images_hdr.resize(sliceIndex + 1);

// Now load the source image.
imagef& src_img = encoder->m_params.m_source_images_hdr[sliceIndex];

return load_image_hdr(data,
arrayBuffer.size(rt),
src_img,
width,
height,
(hdr_image_type)imgType,
ldrSrgbToLinear);
}

void ReactNativeBasisUniversal::setPackUASTCFlags(jsi::Runtime &rt, jsi::Object handle, int flags) {

auto encoder = tryGetBasisEncoder(rt, handle);
assert((flags & cPackUASTCLevelMask) >= cPackUASTCLevelFastest);
assert((flags & cPackUASTCLevelMask) <= cPackUASTCLevelVerySlow);
encoder->m_params.m_pack_uastc_flags = flags;
}

void ReactNativeBasisUniversal::setCompressionLevel(jsi::Runtime &rt, jsi::Object handle, int level) {

auto encoder = tryGetBasisEncoder(rt, handle);
assert(level >= 0 && level <= BASISU_MAX_COMPRESSION_LEVEL);
encoder->m_params.m_compression_level = level;
}

void ReactNativeBasisUniversal::setNormalMap(jsi::Runtime &rt, jsi::Object handle) {
auto encoder = tryGetBasisEncoder(rt, handle);
encoder->m_params.m_perceptual = false;
encoder->m_params.m_mip_srgb = false;
encoder->m_params.m_no_selector_rdo = true;
encoder->m_params.m_no_endpoint_rdo = true;
}

void ReactNativeBasisUniversal::setUASTCHDRQualityLevel(jsi::Runtime &rt, jsi::Object handle, int level) {
auto encoder = tryGetBasisEncoder(rt, handle);
assert((level >= basisu::astc_hdr_codec_options::cMinLevel) && (level <= basisu::astc_hdr_codec_options::cMaxLevel));
encoder->m_params.m_uastc_hdr_options.set_quality_level(level);
}

void ReactNativeBasisUniversal::setSwizzle(jsi::Runtime &rt, jsi::Object handle, int r, int g, int b, int a) {
auto encoder = tryGetBasisEncoder(rt, handle);
assert((r < 4) && (g < 4) && (b < 4) && (a < 4));
encoder->m_params.m_swizzle[0] = static_cast<char>(r);
encoder->m_params.m_swizzle[1] = static_cast<char>(g);
encoder->m_params.m_swizzle[2] = static_cast<char>(b);
encoder->m_params.m_swizzle[3] = static_cast<char>(a);
}

void ReactNativeBasisUniversal::setSelectorRDOThresh(jsi::Runtime &rt, jsi::Object handle, double threshold) {
auto encoder = tryGetBasisEncoder(rt, handle);
encoder->m_params.m_selector_rdo_thresh = static_cast<float>(threshold);
}

void ReactNativeBasisUniversal::setEndpointRDOThresh(jsi::Runtime &rt, jsi::Object handle, double threshold) {
auto encoder = tryGetBasisEncoder(rt, handle);
encoder->m_params.m_endpoint_rdo_thresh = static_cast<float>(threshold);
}

DEFINE_BASIS_ENCODER_PARAMS_SETTER(setCheckForAlpha, m_check_for_alpha, bool);
DEFINE_BASIS_ENCODER_PARAMS_SETTER(setForceAlpha, m_force_alpha, bool);
DEFINE_BASIS_ENCODER_PARAMS_SETTER(setRenormalize, m_renormalize, bool);
DEFINE_BASIS_ENCODER_PARAMS_SETTER(setMaxEndpointClusters, m_max_endpoint_clusters, int);
DEFINE_BASIS_ENCODER_PARAMS_SETTER(setMaxSelectorClusters, m_max_selector_clusters, int);
DEFINE_BASIS_ENCODER_PARAMS_SETTER(setMipSRGB, m_mip_srgb, bool);
DEFINE_BASIS_ENCODER_PARAMS_SETTER(setMipRenormalize, m_mip_renormalize, bool);
DEFINE_BASIS_ENCODER_PARAMS_SETTER(setKTX2SRGBTransferFunc, m_ktx2_srgb_transfer_func, bool);
DEFINE_BASIS_ENCODER_PARAMS_SETTER(setYFlip, m_y_flip, bool);
DEFINE_BASIS_ENCODER_PARAMS_SETTER(setMipGen, m_mip_gen, bool);
DEFINE_BASIS_ENCODER_PARAMS_SETTER(setPerceptual, m_perceptual, bool);
DEFINE_BASIS_ENCODER_PARAMS_SETTER(setUASTC, m_uastc, bool);
DEFINE_BASIS_ENCODER_PARAMS_SETTER(setComputeStats, m_compute_stats, bool);
DEFINE_BASIS_ENCODER_PARAMS_SETTER(setCreateKTX2File, m_create_ktx2_file, bool);
DEFINE_BASIS_ENCODER_PARAMS_SETTER(setDebug, m_debug, bool);
DEFINE_BASIS_ENCODER_PARAMS_SETTER(setHDR, m_hdr, bool);
}
23 changes: 20 additions & 3 deletions cpp/react-native-basis-universal.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,30 @@ class ReactNativeBasisUniversal : public NativeBasisUniversalCxxSpecJSI {
void setUASTC(jsi::Runtime &rt, jsi::Object handle, bool flag) override;
void setCompressionLevel(jsi::Runtime &rt, jsi::Object handle, int level) override;
void setKTX2UASTCSupercompression(jsi::Runtime &rt, jsi::Object handle, bool flag) override;
void setSliceSourceImage(jsi::Runtime &rt, jsi::Object handle, int sliceIndex, jsi::Object imageArray, int width, int height, bool isPng) override;
bool setSliceSourceImage(jsi::Runtime &rt, jsi::Object handle, int sliceIndex, jsi::Object imageArray, int width, int height, bool isPng) override;
void setPackUASTCFlags(jsi::Runtime &rt, jsi::Object handle, int flags) override;
void setQualityLevel(jsi::Runtime &rt, jsi::Object handle, int qualityLevel) override;

int encode(jsi::Runtime &rt, jsi::Object handle, jsi::Object basisFileData) override;
void setPerceptual(jsi::Runtime &rt, jsi::Object handle, bool flag) override;
void setYFlip(jsi::Runtime &rt, jsi::Object handle, bool flag) override;
void setMipGen(jsi::Runtime &rt, jsi::Object handle, bool flag) override;
void setNormalMap(jsi::Runtime &rt, jsi::Object handle) override;
void setKTX2SRGBTransferFunc(jsi::Runtime &rt, jsi::Object handle, bool flag) override;
void setMipSRGB(jsi::Runtime &rt, jsi::Object handle, bool flag) override;
void setMipRenormalize(jsi::Runtime &rt, jsi::Object handle, bool flag) override;
bool setSliceSourceImageHDR(jsi::Runtime &rt, jsi::Object handle, int sliceIndex, jsi::Object imageArray, int width, int height, int imgType, bool ldrSrgbToLinear) override;
void setHDR(jsi::Runtime &rt, jsi::Object handle, bool flag) override;
void setUASTCHDRQualityLevel(jsi::Runtime &rt, jsi::Object handle, int level) override;
void setCheckForAlpha(jsi::Runtime &rt, jsi::Object handle, bool flag) override;
void setForceAlpha(jsi::Runtime &rt, jsi::Object handle, bool flag) override;
void setSwizzle(jsi::Runtime &rt, jsi::Object handle, int r, int g, int b, int a) override;
void setRenormalize(jsi::Runtime &rt, jsi::Object handle, bool flag) override;
void setMaxEndpointClusters(jsi::Runtime &rt, jsi::Object handle, int maxClusters) override;
void setMaxSelectorClusters(jsi::Runtime &rt, jsi::Object handle, int maxClusters) override;
void setSelectorRDOThresh(jsi::Runtime &rt, jsi::Object handle, double threshold) override;
void setEndpointRDOThresh(jsi::Runtime &rt, jsi::Object handle, double threshold) override;

private:
std::shared_ptr<CallInvoker> _callInvoker;
bool basis_initialized_flag;
};

Expand Down
7 changes: 6 additions & 1 deletion example/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ workspace 'BasisUniversalExample.xcworkspace'
options = {
:fabric_enabled => true,
:bridgeless_enabled => true,
:hermes_enabled => true,
}

use_test_app! options
use_test_app! options do |target|
target.app do
pod 'Resources', :path => './Resources'
end
end
Loading

0 comments on commit 8d64f21

Please sign in to comment.