Skip to content

Commit

Permalink
feat: use std::threads for encoding / transcoding operations (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
okwasniewski authored Nov 14, 2024
1 parent a7c8469 commit 6a4a33d
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 79 deletions.
199 changes: 122 additions & 77 deletions cpp/react-native-basis-universal.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "react-native-basis-universal.h"
#include "KTX2File.h"
#include "BasisFile.h"
#include <thread>
#include <future>

#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 All @@ -26,6 +28,12 @@ using namespace basisu;

namespace facebook::react {

struct EncodeResult {
bool success;
std::vector<uint8_t> data;
};


class BasisEncoder : public jsi::NativeState {
public:
basis_compressor_params m_params;
Expand Down Expand Up @@ -89,80 +97,86 @@ int ReactNativeBasisUniversal::encode(jsi::Runtime &rt, jsi::Object handle, jsi:
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;
std::promise<EncodeResult> resultPromise;
std::future<EncodeResult> resultFuture = resultPromise.get_future();

std::thread encodingThread([this,
encoder = std::move(encoder),
promise = std::move(resultPromise)]() mutable {

EncodeResult result{false, std::vector<uint8_t>()};

try {
if (!basis_initialized_flag) {
promise.set_value(std::move(result));
return;
}

unsigned int threads = std::thread::hardware_concurrency();
job_pool jpool(threads);
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)) {
promise.set_value(std::move(result));
return;
}

basis_compressor::error_code ec = comp.process();
if (ec != basis_compressor::cECSuccess) {
promise.set_value(std::move(result));
return;
}

if (params.m_create_ktx2_file) {
auto output = comp.get_output_ktx2_file();
if (!output.data()) {
promise.set_value(std::move(result));
return;
}
result.data.assign(output.data(), output.data() + output.size());
} else {
auto output = comp.get_output_basis_file();
if (!output.data()) {
promise.set_value(std::move(result));
return;
}
result.data.assign(output.data(), output.data() + output.size());
}

result.success = true;
promise.set_value(std::move(result));
} catch (...) {
try {
promise.set_exception(std::current_exception());
} catch (...) {}
}

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()) {
});

encodingThread.detach();

try {
auto result = resultFuture.get();
if (!result.success) {
return 0;
}

// Compression succeeded, so copy the .basis file bytes to the caller's buffer.
auto arrayBuffer = basisFileData.getArrayBuffer(rt);
auto outputBuffer = jsi::ArrayBuffer(std::move(arrayBuffer));
memcpy(outputBuffer.data(rt), output.data(), output.size());
memcpy(outputBuffer.data(rt), result.data.data(), result.data.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 result.data.size();
} catch (const std::exception& e) {
throw jsi::JSError(rt, e.what());
}

return 0;
}


void ReactNativeBasisUniversal::setKTX2UASTCSupercompression(jsi::Runtime &rt, jsi::Object handle, bool flag) {
auto encoder = tryGetBasisEncoder(rt, handle);
encoder->m_params.m_ktx2_uastc_supercompression = flag ? basist::KTX2_SS_ZSTANDARD : basist::KTX2_SS_NONE;
Expand Down Expand Up @@ -216,6 +230,7 @@ bool ReactNativeBasisUniversal::setSliceSourceImage(jsi::Runtime &rt, jsi::Objec
return true;
}


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");
Expand Down Expand Up @@ -407,15 +422,47 @@ int ReactNativeBasisUniversal::getImageTranscodedSizeInBytes(jsi::Runtime &rt, j

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,
dst,
levelIndex,
layerIndex,
faceIndex,
format,
getAlphaForOpaqueFormats,
channel0,
channel1);

std::promise<int> resultPromise;
std::future<int> resultFuture = resultPromise.get_future();

std::thread transcodingThread([
this,
ktx2Handle = ktx2Handle,
promise = std::move(resultPromise),
&rt,
&dst,
levelIndex,
layerIndex,
faceIndex,
format,
getAlphaForOpaqueFormats,
channel0,
channel1
]() mutable {
try {
auto res = ktx2Handle->transcodeImage(rt,
dst,
levelIndex,
layerIndex,
faceIndex,
format,
getAlphaForOpaqueFormats,
channel0,
channel1);
promise.set_value(res);
} catch (...) {
promise.set_exception(std::current_exception());
}
});

transcodingThread.detach();

try {
return resultFuture.get();
} catch (const std::exception& e) {
throw jsi::JSError(rt, e.what());
}
}


Expand Down Expand Up @@ -507,6 +554,4 @@ jsi::Object ReactNativeBasisUniversal::getImageLevelDescBasisFile(jsi::Runtime &
return jsi::Object(rt);
}



}
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1237,7 +1237,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-basis-universal (0.1.0):
- react-native-basis-universal (0.2.1):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -1845,7 +1845,7 @@ SPEC CHECKSUMS:
React-logger: d79b704bf215af194f5213a6b7deec50ba8e6a9b
React-Mapbuffer: b982d5bba94a8bc073bda48f0d27c9b28417fae3
React-microtasksnativemodule: 2cec1d6e126598df0f165268afa231174dd1a611
react-native-basis-universal: 228f3b3e396df77064173f7a26622d45e424964e
react-native-basis-universal: 8c4230f3b21c9cb23109ba9d8aff32deb19cd1a6
react-native-blob-util: e6a3b23a99ac2c3d9fa48722db049a1e1808efc2
React-nativeconfig: 8c83d992b9cc7d75b5abe262069eaeea4349f794
React-NativeModulesApple: 9f7920224a3b0c7d04d77990067ded14cee3c614
Expand Down

0 comments on commit 6a4a33d

Please sign in to comment.