TiagoMSSantos/MobileRT

View on GitHub
app/System_dependent/Android_JNI/JNI_layer.cpp

Summary

Maintainability
Test Coverage
#include "JNI_layer.hpp"

#include "Components/Cameras/Orthographic.hpp"
#include "Components/Cameras/Perspective.hpp"
#include "Components/Lights/AreaLight.hpp"
#include "Components/Lights/PointLight.hpp"
#include "Components/Loaders/CameraFactory.hpp"
#include "Components/Loaders/OBJLoader.hpp"
#include "Components/Loaders/PerspectiveLoader.hpp"
#include "Components/Samplers/Constant.hpp"
#include "Components/Samplers/HaltonSeq.hpp"
#include "Components/Samplers/MersenneTwister.hpp"
#include "Components/Samplers/PCG.hpp"
#include "Components/Samplers/StaticHaltonSeq.hpp"
#include "Components/Samplers/StaticMersenneTwister.hpp"
#include "Components/Samplers/StaticPCG.hpp"
#include "Components/Samplers/Stratified.hpp"
#include "Components/Shaders/DepthMap.hpp"
#include "Components/Shaders/DiffuseMaterial.hpp"
#include "Components/Shaders/NoShadows.hpp"
#include "Components/Shaders/PathTracer.hpp"
#include "Components/Shaders/Whitted.hpp"
#include "MobileRT/Renderer.hpp"
#include "MobileRT/Scene.hpp"
#include "Scenes/Scenes.hpp"

#include <android/bitmap.h>
#include <condition_variable>
#include <fstream>
#include <istream>
#include <glm/glm.hpp>
#include <mutex>
#include <string>
#include <unistd.h>

/**
 * The number of frames per second.
 */
static float fps_ {};

/**
 * The current state of the MobileRT engine.
 */
static ::std::atomic<::State> state_ {State::IDLE};

/**
 * The MobileRT Renderer.
 */
static ::std::unique_ptr<::MobileRT::Renderer> renderer_ {};

/**
 * A Java Virtual Machine.
 */
static ::std::unique_ptr<::JavaVM> javaVM_ {};

/**
 * The auxiliary thread to execute the MobileRT render.
 */
static ::std::unique_ptr<::std::thread> thread_ {};

/**
 * The mutex for the MobileRT to only 1 thread execute it.
 */
static ::std::mutex mutex_ {};

/**
 * The number of lights in the current scene.
 */
static ::std::int32_t numLights_ {};

/**
 * The elapsed time in milliseconds to create the Shader and thus the Acceleration Structure.
 */
static ::std::int64_t timeRenderer_ {};

/**
 * The condition variable to wait for the MobileRT Renderer to finish the rendering process.
 */
static ::std::condition_variable rendered_ {};

/**
 * Whether or not the rendering process was finished.
 */
static ::std::atomic<bool> finishedRendering_ {true};

/**
 * The definition of the OBJ file.
 */
static ::std::string objDefinition_ {};

/**
 * The definition of the MTL file.
 */
static ::std::string mtlDefinition_ {};

/**
 * The definition of the CAM file.
 */
static ::std::string camDefinition_ {};

/**
 * The cache for textures.
 */
static ::std::unordered_map<::std::string, ::MobileRT::Texture> texturesCache_ {};


/**
 * Helper method that throws a Java exception.
 *
 * @param env           The JNI environment.
 * @param exception     The exception which contains the message to add.
 * @param exceptionName The name of the exception class to throw.
 */
static void handleException(JNIEnv *const env,
                            const ::std::exception &exception,
                            const char *const exceptionName) {
    const jclass clazz {env->FindClass(exceptionName)};
    const jint res {env->ThrowNew(clazz, exception.what())};
    if (res != 0) {
        LOG_ERROR("ERROR: ", res);
    } else {
        LOG_ERROR(exceptionName, " thrown");
    }
    state_ = State::IDLE;
    finishedRendering_ = true;
}

extern "C"
::std::int32_t JNI_OnLoad(JavaVM *const jvm, void * /*reserved*/) {
    // Reset errno to avoid "errno EINVAL (22): Invalid argument".
    errno = 0;
    MobileRT::checkSystemError("JNI_OnLoad start");
    LOG_DEBUG("JNI_OnLoad");
    javaVM_.reset(jvm);

    JNIEnv *jniEnv {};
    {
        const jint result {javaVM_->GetEnv(reinterpret_cast<void **> (&jniEnv), JNI_VERSION_1_6)};
        ASSERT(result == JNI_OK, "JNI was not loaded properly.");
        static_cast<void> (result);
    }
    ASSERT(jniEnv != nullptr, "JNIEnv was not loaded properly.");
    jniEnv->ExceptionClear();
    MobileRT::checkSystemError("JNI_OnLoad finish");
    return JNI_VERSION_1_6;
}

extern "C"
void JNI_OnUnload(JavaVM *const /*jvm*/, void * /*reserved*/) {
    LOG_DEBUG("JNI_OnUnload");
}

extern "C"
jobject Java_puscas_mobilertapp_MainRenderer_rtInitCameraArray(
    JNIEnv *env,
    jobject /*thiz*/
) {
    MobileRT::checkSystemError("rtInitCameraArray start");
    try {
        jobject directBuffer {};
        {
            const ::std::lock_guard<::std::mutex> lock {mutex_};
            if (renderer_ != nullptr) {
                ::MobileRT::Camera *const camera {renderer_->camera_.get()};
                const ::std::int64_t arraySize {20};
                const jlong arrayBytes {arraySize * sizeof(jfloat)};

                float *const floatBuffer {new float[arraySize]};

                if (floatBuffer != nullptr) {
                    directBuffer = env->NewDirectByteBuffer(floatBuffer, arrayBytes);
                    if (directBuffer != nullptr) {
                        ::std::int32_t i{};

                        floatBuffer[i++] = camera->position_.x;
                        floatBuffer[i++] = camera->position_.y;
                        floatBuffer[i++] = camera->position_.z;
                        floatBuffer[i++] = 1.0F;

                        floatBuffer[i++] = camera->direction_.x;
                        floatBuffer[i++] = camera->direction_.y;
                        floatBuffer[i++] = camera->direction_.z;
                        floatBuffer[i++] = 1.0F;

                        floatBuffer[i++] = camera->up_.x;
                        floatBuffer[i++] = camera->up_.y;
                        floatBuffer[i++] = camera->up_.z;
                        floatBuffer[i++] = 1.0F;

                        floatBuffer[i++] = camera->right_.x;
                        floatBuffer[i++] = camera->right_.y;
                        floatBuffer[i++] = camera->right_.z;
                        floatBuffer[i++] = 1.0F;

                        const ::Components::Perspective *const perspective {dynamic_cast<::Components::Perspective *> (camera)};
                        const ::Components::Orthographic *const orthographic {dynamic_cast<::Components::Orthographic *> (camera)};
                        if (perspective != nullptr) {
                            const float hFov{perspective->getHFov()};
                            const float vFov{perspective->getVFov()};
                            floatBuffer[i++] = hFov;
                            floatBuffer[i++] = vFov;
                            floatBuffer[i++] = 0.0F;
                            floatBuffer[i++] = 0.0F;
                        }
                        if (orthographic != nullptr) {
                            const float sizeH{orthographic->getSizeH()};
                            const float sizeV{orthographic->getSizeV()};
                            floatBuffer[i++] = 0.0F;
                            floatBuffer[i++] = 0.0F;
                            floatBuffer[i++] = sizeH;
                            floatBuffer[i] = sizeV;
                        }
                        if (perspective == nullptr && orthographic == nullptr) {
                            const ::std::string errorMessage {"Scene provided to MobileRT doesn't have any type of camera."};
                            throw ::std::runtime_error {errorMessage};
                        }
                    } else {
                        const ::std::string errorMessage {"JNIEnv::NewDirectByteBuffer failed to allocate native memory!"};
                        throw ::std::runtime_error {errorMessage};
                    }
                } else {
                    const ::std::string errorMessage {"C++ failed to allocate native memory!"};
                    throw ::std::runtime_error {errorMessage};
                }

            }
            env->ExceptionClear();
        }
        MobileRT::checkSystemError("rtInitCameraArray finish");
        return directBuffer;
    } catch (const ::std::bad_alloc &badAlloc) {
        handleException(env, badAlloc, "puscas/mobilertapp/exceptions/LowMemoryException");
    } catch (const ::std::exception &exception) {
        handleException(env, exception, "java/lang/RuntimeException");
    } catch (...) {
        handleException(env, ::std::exception{}, "java/lang/RuntimeException");
    }
    MobileRT::checkSystemError("rtInitCameraArray finish");
    return nullptr;
}

extern "C"
jobject Java_puscas_mobilertapp_MainRenderer_rtInitVerticesArray(
    JNIEnv *env,
    jobject /*thiz*/
) {
    MobileRT::checkSystemError("rtInitVerticesArray start");
    try {
        jobject directBuffer {};
        {
            const ::std::lock_guard<::std::mutex> lock {mutex_};
            if (renderer_ != nullptr) {
                const ::std::vector<::MobileRT::Triangle> &triangles {renderer_->shader_->getTriangles()};
                const ::std::uint32_t arraySize {static_cast<::std::uint32_t> (triangles.size() * 3 * 4)};
                const jlong arrayBytes {arraySize * static_cast<jlong> (sizeof(jfloat))};

                float *const floatBuffer {new float[arraySize]};

                if (floatBuffer != nullptr) {
                    directBuffer = env->NewDirectByteBuffer(floatBuffer, arrayBytes);
                    if (directBuffer != nullptr) {
                        ::std::int32_t i {};
                        for (const ::MobileRT::Triangle &triangle : triangles) {
                            const ::glm::vec4 &pointA {triangle.getA().x,
                                                      triangle.getA().y,
                                                      triangle.getA().z, 1.0F};
                            const ::glm::vec4 &pointB {pointA.x + triangle.getAB().x,
                                                      pointA.y + triangle.getAB().y,
                                                      pointA.z + triangle.getAB().z, 1.0F};
                            const ::glm::vec4 &pointC {pointA.x + triangle.getAC().x,
                                                      pointA.y + triangle.getAC().y,
                                                      pointA.z + triangle.getAC().z, 1.0F};

                            floatBuffer[i++] = pointA.x;
                            floatBuffer[i++] = pointA.y;
                            floatBuffer[i++] = -pointA.z;
                            floatBuffer[i++] = pointA.w;

                            floatBuffer[i++] = pointB.x;
                            floatBuffer[i++] = pointB.y;
                            floatBuffer[i++] = -pointB.z;
                            floatBuffer[i++] = pointB.w;

                            floatBuffer[i++] = pointC.x;
                            floatBuffer[i++] = pointC.y;
                            floatBuffer[i++] = -pointC.z;
                            floatBuffer[i++] = pointC.w;
                        }
                    } else {
                        const ::std::string errorMessage {"JNIEnv::NewDirectByteBuffer failed to allocate native memory!"};
                        throw ::std::runtime_error {errorMessage};
                    }
                } else {
                    const ::std::string errorMessage {"C++ failed to allocate native memory!"};
                    throw ::std::runtime_error {errorMessage};
                }
            }
            env->ExceptionClear();
        }
        MobileRT::checkSystemError("rtInitVerticesArray finish");
        return directBuffer;
    } catch (const ::std::bad_alloc &badAlloc) {
        handleException(env, badAlloc, "puscas/mobilertapp/exceptions/LowMemoryException");
    } catch (const ::std::exception &exception) {
        handleException(env, exception, "java/lang/RuntimeException");
    } catch (...) {
        handleException(env, ::std::exception{}, "java/lang/RuntimeException");
    }
    MobileRT::checkSystemError("rtInitVerticesArray finish");
    return nullptr;
}

extern "C"
jobject Java_puscas_mobilertapp_MainRenderer_rtInitColorsArray(
    JNIEnv *env,
    jobject /*thiz*/
) {
    MobileRT::checkSystemError("rtInitColorsArray start");
    try {
        jobject directBuffer {};
        {
            const ::std::lock_guard<::std::mutex> lock {mutex_};
            if (renderer_ != nullptr) {
                const ::std::vector<::MobileRT::Triangle> &triangles {renderer_->shader_->getTriangles()};
                const ::std::uint32_t arraySize {static_cast<::std::uint32_t> (triangles.size() * 3 * 4)};
                const jlong arrayBytes {arraySize * static_cast<::std::int64_t> (sizeof(jfloat))};

                float *const floatBuffer {new float[arraySize]};

                if (floatBuffer != nullptr) {
                    directBuffer = env->NewDirectByteBuffer(floatBuffer, arrayBytes);
                    if (directBuffer != nullptr) {
                        ::std::int32_t i {};
                        for (const ::MobileRT::Triangle &triangle : triangles) {
                            const ::std::int32_t materialIndex {triangle.getMaterialIndex()};
                            ::MobileRT::Material material {::MobileRT::Material{}};
                            if (materialIndex >= 0) {
                                material = renderer_->shader_->getMaterials()[static_cast<::std::uint32_t> (materialIndex)];
                            }

                            const ::glm::vec3 &kD{material.Kd_};
                            const ::glm::vec3 &kS{material.Ks_};
                            const ::glm::vec3 &kT{material.Kt_};
                            const ::glm::vec3 &lE{material.Le_};
                            ::glm::vec3 color {kD};

                            color = ::glm::all(::glm::greaterThan(kS, color)) ? kS : color;
                            color = ::glm::all(::glm::greaterThan(kT, color)) ? kT : color;
                            color = ::glm::all(::glm::greaterThan(lE, color)) ? lE : color;

                            floatBuffer[i++] = color.r;
                            floatBuffer[i++] = color.g;
                            floatBuffer[i++] = color.b;
                            floatBuffer[i++] = 1.0F;

                            floatBuffer[i++] = color.r;
                            floatBuffer[i++] = color.g;
                            floatBuffer[i++] = color.b;
                            floatBuffer[i++] = 1.0F;

                            floatBuffer[i++] = color.r;
                            floatBuffer[i++] = color.g;
                            floatBuffer[i++] = color.b;
                            floatBuffer[i++] = 1.0F;
                        }
                    } else {
                        const ::std::string errorMessage {"JNIEnv::NewDirectByteBuffer failed to allocate native memory!"};
                        throw ::std::runtime_error {errorMessage};
                    }
                } else {
                    const ::std::string errorMessage {"C++ failed to allocate native memory!"};
                    throw ::std::runtime_error {errorMessage};
                }

            }
            env->ExceptionClear();
        }
        MobileRT::checkSystemError("rtInitColorsArray finish 1");
        return directBuffer;
    } catch (const ::std::bad_alloc &badAlloc) {
        handleException(env, badAlloc, "puscas/mobilertapp/exceptions/LowMemoryException");
    } catch (const ::std::exception &exception) {
        handleException(env, exception, "java/lang/RuntimeException");
    } catch (...) {
        handleException(env, ::std::exception{}, "java/lang/RuntimeException");
    }
    MobileRT::checkSystemError("rtInitColorsArray finish 2");
    return nullptr;
}

static void updateFps() {
    MobileRT::checkSystemError("updateFps start");
    static ::std::int32_t frame {};
    static ::std::chrono::steady_clock::time_point timebase {};
    ++frame;
    const ::std::chrono::steady_clock::time_point timeNow {::std::chrono::steady_clock::now()};
    const long long timeElapsed {::std::chrono::duration_cast<::std::chrono::milliseconds>(timeNow - timebase).count()};
    fps_ = (static_cast<float>(frame) * 1000.0F) / static_cast<float>(timeElapsed);
    if (timeElapsed > 1000) {
        timebase = timeNow;
        frame = 0;
    }
    MobileRT::checkSystemError("updateFps finish");
}

extern "C"
void Java_puscas_mobilertapp_DrawView_rtStartRender(
    JNIEnv *env,
    jobject /*thiz*/,
    jboolean wait
) {
    try {
        MobileRT::checkSystemError("rtStartRender start");
        if (wait) {
            ::std::unique_lock<::std::mutex> lock {mutex_};
            rendered_.wait(lock, [&] { return finishedRendering_.load(); });
            finishedRendering_ = false;
        }
        state_ = State::BUSY;
        LOG_DEBUG("STATE = BUSY");
        env->ExceptionClear();
        MobileRT::checkSystemError("rtStartRender finish");
    } catch (const ::std::exception &exception) {
        handleException(env, exception, "java/lang/RuntimeException");
    }
}

extern "C"
void Java_puscas_mobilertapp_DrawView_rtStopRender(
    JNIEnv *env,
    jobject /*thiz*/,
    jboolean wait
) {
    MobileRT::checkSystemError("rtStopRender start");
    {
        LOG_DEBUG("Will get lock");
        state_ = State::STOPPED;
        LOG_DEBUG("STATE = STOPPED");
        ::std::unique_lock<::std::mutex> lock {mutex_};
        LOG_DEBUG("Got lock, waiting for renderer to finish");
        if (renderer_ != nullptr) {
            LOG_DEBUG("RENDERER STOP");
            renderer_->stopRender();
        }
        if (wait) {
            while (!finishedRendering_) {
                LOG_DEBUG("WILL TRY TO STOP RENDERER");
                if (renderer_ != nullptr) {
                    LOG_DEBUG("RENDERER STOP");
                    renderer_->stopRender();
                    break;
                }
                rendered_.wait_for(lock, ::std::chrono::seconds(3),
                                   [&] { return finishedRendering_.load(); });
            }
        }
        LOG_DEBUG("Renderer finished");
    }
    env->ExceptionClear();
    LOG_DEBUG("rtStopRender finished");
    MobileRT::checkSystemError("rtStopRender finish");
}

extern "C"
jint Java_puscas_mobilertapp_MainRenderer_rtInitialize(
    JNIEnv *env,
    jobject /*thiz*/,
    jobject localConfig
) {
    MobileRT::checkSystemError("rtInitialize start");
    LOG_DEBUG("INITIALIZE");
    try {
        const jclass configClass {env->GetObjectClass(localConfig)};

        const jmethodID sceneMethodId {env->GetMethodID(configClass, "getScene", "()I")};
        const jint sceneIndex {env->CallIntMethod(localConfig, sceneMethodId)};
        LOG_DEBUG("sceneIndex: ", sceneIndex);

        const jmethodID shaderMethodId {env->GetMethodID(configClass, "getShader", "()I")};
        const jint shaderIndex {env->CallIntMethod(localConfig, shaderMethodId)};
        LOG_DEBUG("shaderIndex: ", shaderIndex);

        const jmethodID acceleratorMethodId {env->GetMethodID(configClass, "getAccelerator", "()I")};
        const jint acceleratorIndex {env->CallIntMethod(localConfig, acceleratorMethodId)};
        LOG_DEBUG("acceleratorIndex: ", acceleratorIndex);


        const jmethodID configResolutionMethodId {env->GetMethodID(configClass, "getConfigResolution",
                                                         "()Lpuscas/mobilertapp/configs/ConfigResolution;")};
        const jobject resolutionConfig {env->CallObjectMethod(localConfig, configResolutionMethodId)};
        const jclass resolutionConfigClass {env->GetObjectClass(resolutionConfig)};

        const jmethodID widthMethodId {env->GetMethodID(resolutionConfigClass, "getWidth", "()I")};
        const jint width {env->CallIntMethod(resolutionConfig, widthMethodId)};
        LOG_DEBUG("width: ", width);

        const jmethodID heightMethodId {env->GetMethodID(resolutionConfigClass, "getHeight", "()I")};
        const jint height {env->CallIntMethod(resolutionConfig, heightMethodId)};
        LOG_DEBUG("height: ", height);


        const jmethodID configSamplesMethodId {env->GetMethodID(configClass, "getConfigSamples",
                                                      "()Lpuscas/mobilertapp/configs/ConfigSamples;")};
        const jobject samplesConfig {env->CallObjectMethod(localConfig, configSamplesMethodId)};
        const jclass samplesConfigClass {env->GetObjectClass(samplesConfig)};

        const jmethodID samplesPixelMethodId {env->GetMethodID(samplesConfigClass, "getSamplesPixel", "()I")};
        const jint samplesPixel {env->CallIntMethod(samplesConfig, samplesPixelMethodId)};
        LOG_DEBUG("samplesPixel: ", samplesPixel);

        const jmethodID samplesLightMethodId {env->GetMethodID(samplesConfigClass, "getSamplesLight", "()I")};
        const jint samplesLight {env->CallIntMethod(samplesConfig, samplesLightMethodId)};
        LOG_DEBUG("samplesLight: ", samplesLight);

        jboolean isCopy {JNI_FALSE};
        const jmethodID objMethodId {env->GetMethodID(configClass, "getObjFilePath", "()Ljava/lang/String;")};
        const jstring localObjFilePath {reinterpret_cast<jstring> (env->CallObjectMethod(localConfig, objMethodId))};
        const ::std::string objFilePath {env->GetStringUTFChars(localObjFilePath, &isCopy)};
        LOG_DEBUG("objFilePath: ", objFilePath);

        const ::std::int32_t res {
            [&]() -> ::std::int32_t {
                LOG_DEBUG("Acquiring lock");
                const ::std::lock_guard<::std::mutex> lock {mutex_};
                renderer_ = nullptr;
                const float ratio {static_cast<float> (width) / static_cast<float> (height)};
                ::MobileRT::Scene scene {};
                ::std::unique_ptr<::MobileRT::Sampler> samplerPixel {};
                ::std::unique_ptr<::MobileRT::Shader> shader {};
                ::std::unique_ptr<::MobileRT::Camera> camera {};
                ::glm::vec3 maxDist {};
                LOG_DEBUG("LOADING SCENE: ", sceneIndex);
                switch (sceneIndex) {
                    case 0:
                        scene = cornellBox_Scene(::std::move(scene));
                        camera = cornellBox_Cam(ratio);
                        maxDist = ::glm::vec3 {1, 1, 1};
                        break;

                    case 1:
                        scene = spheres_Scene(::std::move(scene));
                        camera = spheres_Cam(ratio);
                        maxDist = ::glm::vec3 {8, 8, 8};
                        break;

                    case 2:
                        scene = cornellBox2_Scene(::std::move(scene));
                        camera = cornellBox_Cam(ratio);
                        maxDist = ::glm::vec3 {1, 1, 1};
                        break;

                    case 3:
                        scene = spheres2_Scene(::std::move(scene));
                        camera = spheres2_Cam(ratio);
                        maxDist = ::glm::vec3 {8, 8, 8};
                        break;

                    default: {
                        if (objDefinition_.empty()) {
                            LOG_DEBUG("OBJ file not read!");
                            throw ::std::runtime_error {"OBJ file not read!"};
                        }
                        if (mtlDefinition_.empty()) {
                            LOG_DEBUG("MTL file not read!");
                        }
                        if (camDefinition_.empty()) {
                            LOG_DEBUG("CAM file not read!");
                        }

                        ::Components::CameraFactory cameraFactory {::Components::CameraFactory()};
                        const ::std::istringstream isCam {camDefinition_};
                        ::std::istream iCam {isCam.rdbuf()};
                        camera = cameraFactory.loadFromFile(iCam, ratio);

                        const ::std::istringstream isObj {objDefinition_};
                        const ::std::istringstream isMtl {mtlDefinition_};
                        ::std::istream iObj {isObj.rdbuf()};
                        ::std::istream iMtl {isMtl.rdbuf()};
                        ::Components::OBJLoader objLoader {iObj, iMtl};
                        objDefinition_.clear();
                        mtlDefinition_.clear();
                        camDefinition_.clear();
                        iObj.clear();
                        iMtl.clear();
                        objDefinition_.erase();
                        mtlDefinition_.erase();
                        camDefinition_.erase();
                        objDefinition_.shrink_to_fit();
                        mtlDefinition_.shrink_to_fit();
                        camDefinition_.shrink_to_fit();

                        MobileRT::checkSystemError("rtInitialize after loading OBJ");
                        LOG_DEBUG("OBJLOADER PROCESSED");

                        if (!objLoader.isProcessed()) {
                            LOG_ERROR("OBJLOADER could not load the scene.");
                            return -1;
                        }
                        const bool sceneBuilt {objLoader.fillScene(
                            &scene,
                            []() {return ::MobileRT::std::make_unique<Components::StaticPCG>();},
                            objFilePath,
                            texturesCache_
                        )};
                        texturesCache_.clear();
                        MobileRT::checkSystemError("rtInitialize after filling scene");
                        if (!sceneBuilt) {
                            LOG_ERROR("OBJLOADER could not load the scene.");
                            return -1;
                        }

                        maxDist = ::glm::vec3{1, 1, 1};
                    }
                        break;
                }
                samplerPixel = samplesPixel <= 1
                   ? ::std::unique_ptr<::MobileRT::Sampler>(::MobileRT::std::make_unique<Components::Constant>(0.5F))
                   : ::std::unique_ptr<::MobileRT::Sampler>(::MobileRT::std::make_unique<Components::StaticPCG>());
                LOG_DEBUG("LOADING SHADER: ", shaderIndex);
                LOG_DEBUG("LOADING ACCELERATOR: ", ::MobileRT::Shader::Accelerator(acceleratorIndex));
                LOG_DEBUG("samplesLight: ", samplesLight);
                MobileRT::checkSystemError("rtInitialize before loading shader");
                const ::std::chrono::time_point<::std::chrono::system_clock> chronoStart {::std::chrono::system_clock::now()};
                switch (shaderIndex) {
                    case 1: {
                        shader = ::MobileRT::std::make_unique<Components::Whitted>(
                            ::std::move(scene),
                            samplesLight,
                            ::MobileRT::Shader::Accelerator(acceleratorIndex)
                        );
                        break;
                    }

                    case 2: {
                        ::std::unique_ptr<MobileRT::Sampler> samplerRussianRoulette{
                            ::MobileRT::std::make_unique<Components::StaticPCG>()
                        };

                        shader = ::MobileRT::std::make_unique<Components::PathTracer>(
                            ::std::move(scene),
                            ::std::move(samplerRussianRoulette),
                            samplesLight,
                            ::MobileRT::Shader::Accelerator(acceleratorIndex)
                        );
                        break;
                    }

                    case 3: {
                        shader = ::MobileRT::std::make_unique<Components::DepthMap>(
                            ::std::move(scene), maxDist,
                            ::MobileRT::Shader::Accelerator(acceleratorIndex)
                        );
                        break;
                    }

                    case 4: {
                        shader = ::MobileRT::std::make_unique<Components::DiffuseMaterial>(
                            ::std::move(scene), ::MobileRT::Shader::Accelerator(acceleratorIndex)
                        );
                        break;
                    }

                    default: {
                        shader = ::MobileRT::std::make_unique<Components::NoShadows>(
                            ::std::move(scene),
                            samplesLight,
                            ::MobileRT::Shader::Accelerator(acceleratorIndex)
                        );
                        break;
                    }
                }
                const ::std::chrono::time_point<::std::chrono::system_clock> chronoEnd {::std::chrono::system_clock::now()};
                MobileRT::checkSystemError("rtInitialize after loading shader");

                LOG_DEBUG("LOADING RENDERER");
                const ::std::int32_t planes {static_cast<::std::int32_t> (shader->getPlanes().size())};
                const ::std::int32_t spheres {static_cast<::std::int32_t> (shader->getSpheres().size())};
                const ::std::int32_t triangles {static_cast<::std::int32_t> (shader->getTriangles().size())};
                const ::std::int32_t materials {static_cast<::std::int32_t> (shader->getMaterials().size())};
                numLights_ = static_cast<::std::int32_t> (shader->getLights().size());
                const ::std::int32_t nPrimitives {triangles + spheres + planes};
                LOG_INFO("PLANES = ", planes);
                LOG_INFO("SPHERES = ", spheres);
                LOG_INFO("TRIANGLES = ", triangles);
                LOG_INFO("LIGHTS = ", numLights_);
                LOG_INFO("MATERIALS = ", materials);
                LOG_INFO("TOTAL PRIMITIVES = ", nPrimitives);
                LOG_INFO("width = ", width);
                LOG_INFO("height = ", height);
                LOG_INFO("samplesPixel = ", samplesPixel);
                renderer_ = ::MobileRT::std::make_unique<::MobileRT::Renderer>(
                    ::std::move(shader), ::std::move(camera), ::std::move(samplerPixel),
                    width, height, samplesPixel
                );
                MobileRT::checkSystemError("Renderer was built.");
                timeRenderer_ = ::std::chrono::duration_cast<::std::chrono::milliseconds>(chronoEnd - chronoStart).count();
                LOG_INFO("TIME CONSTRUCTION RENDERER = ", timeRenderer_, "ms");
                MobileRT::checkSystemError("rtInitialize almost finished");
                return nPrimitives;
            }()};

        env->ExceptionClear();
        LOG_DEBUG("PRIMITIVES = ", res);
        MobileRT::checkSystemError("rtInitialize finish");
        return res;
    } catch (const ::std::bad_alloc &badAlloc) {
        handleException(env, badAlloc, "puscas/mobilertapp/exceptions/LowMemoryException");
        return -1;
    } catch (const ::std::exception &exception) {
        handleException(env, exception, "java/lang/RuntimeException");
        return -2;
    } catch (...) {
        handleException(env, ::std::exception {}, "java/lang/RuntimeException");
        return -3;
    }
}

extern "C"
void Java_puscas_mobilertapp_MainRenderer_rtFinishRender(
    JNIEnv *env,
    jobject /*thiz*/
) {
    MobileRT::checkSystemError("rtFinishRender start");

    {
        const ::std::lock_guard<::std::mutex> lock {mutex_};
        state_ = State::FINISHED;
        LOG_DEBUG("STATE = FINISHED");
        if (renderer_ != nullptr) {
            LOG_DEBUG("RENDERER STOP");
            renderer_->stopRender();
        }
        state_ = State::IDLE;
        LOG_DEBUG("STATE = IDLE");
        fps_ = 0.0F;
        timeRenderer_ = 0;
        finishedRendering_ = true;
        env->ExceptionClear();
    }
    MobileRT::checkSystemError("rtFinishRender finish");
}

extern "C"
void Java_puscas_mobilertapp_MainRenderer_rtRenderIntoBitmap(
    JNIEnv *env,
    jobject /*thiz*/,
    jobject localBitmap,
    jint nThreads
) {
    MobileRT::checkSystemError("rtRenderIntoBitmap start");
    LOG_DEBUG("rtRenderIntoBitmap");
    LOG_DEBUG("nThreads = ", nThreads);
    try {
        jobject globalBitmap {static_cast<jobject> (env->NewGlobalRef(localBitmap))};

        const ::std::function<void()> lambda {
            [=]() -> void {
                ::std::chrono::duration<double> timeRendering {};

                LOG_DEBUG("rtRenderIntoBitmap step 1");
                ASSERT(env != nullptr, "JNIEnv not valid.");
                MobileRT::checkSystemError("rtRenderIntoBitmap step 1");
                const jint jniError {
                    javaVM_->GetEnv(reinterpret_cast<void **> (const_cast<JNIEnv **> (&env)), JNI_VERSION_1_6)
                };

                LOG_DEBUG("rtRenderIntoBitmap step 2: ", jniError);
                ASSERT(jniError == JNI_OK || jniError == JNI_EDETACHED, "JNIEnv not valid.");
                static_cast<void>(jniError);
                {
                    MobileRT::checkSystemError("rtRenderIntoBitmap step 2");
                    const jint result {javaVM_->AttachCurrentThread(const_cast<JNIEnv **> (&env), nullptr)};
                    if (errno == EINVAL) {
                        // Ignore invalid argument (necessary for Android API 16)
                        errno = 0;
                    }
                    MobileRT::checkSystemError("rtRenderIntoBitmap step 3");
                    ASSERT(result == JNI_OK, "Couldn't attach current thread to JVM.");
                    static_cast<void> (result);
                }

                LOG_DEBUG("rtRenderIntoBitmap step 3");
                ::std::int32_t *dstPixels {};
                {
                    const jint ret {
                        AndroidBitmap_lockPixels(env, globalBitmap,
                                                 reinterpret_cast<void **> (&dstPixels))
                    };
                    ASSERT(ret == JNI_OK, "Couldn't lock the Android bitmap pixels.");
                    LOG_DEBUG("ret = ", ret);
                    static_cast<void> (ret);
                }

                LOG_DEBUG("rtRenderIntoBitmap step 4");
                AndroidBitmapInfo info {};
                {
                    MobileRT::checkSystemError("rtRenderIntoBitmap step 4");
                    const jint ret {AndroidBitmap_getInfo(env, globalBitmap, &info)};
                    ASSERT(ret == JNI_OK, "Couldn't get the Android bitmap information structure.");
                    LOG_DEBUG("ret = ", ret);
                    static_cast<void> (ret);
                }

                LOG_DEBUG("rtRenderIntoBitmap step 5");
                ::std::int32_t rep {1};
                {
                    const jint result {javaVM_->DetachCurrentThread()};
                    ASSERT(result == JNI_OK, "Couldn't detach the current thread from JVM.");
                    static_cast<void> (result);
                }
                LOG_DEBUG("WILL START TO RENDER");
                MobileRT::checkSystemError("starting render timer");
                const ::std::chrono::time_point<::std::chrono::system_clock> chronoStartRendering {::std::chrono::system_clock::now()};
                while (state_ == State::BUSY && rep > 0) {
                    LOG_DEBUG("STARTING RENDERING");
                    LOG_DEBUG("nThreads = ", nThreads);
                    {
                        if (renderer_ != nullptr) {
                            MobileRT::checkSystemError("starting renderFrame");
                            renderer_->renderFrame(dstPixels, nThreads);
                            MobileRT::checkSystemError("renderFrame done");
                        }
                    }
                    LOG_DEBUG("FINISHED RENDERING");
                    updateFps();
                    rep--;
                }
                const ::std::chrono::time_point<::std::chrono::system_clock> chronoEndRendering {::std::chrono::system_clock::now()};
                timeRendering = chronoEndRendering - chronoStartRendering;
                LOG_DEBUG("RENDER FINISHED");
                finishedRendering_ = true;
                rendered_.notify_all();
                {
                    const ::std::lock_guard<::std::mutex> lock {mutex_};
                    if (state_ != State::STOPPED) {
                        state_ = State::FINISHED;
                        LOG_DEBUG("STATE = FINISHED");
                    }
                    {
                        MobileRT::checkSystemError("rtRenderIntoBitmap step 6");
                        const jint result {javaVM_->AttachCurrentThread(const_cast<JNIEnv **> (&env), nullptr)};
                        if (errno == EINVAL) {
                            // Ignore invalid argument (necessary for Android API 16)
                            errno = 0;
                        }
                        MobileRT::checkSystemError("rtRenderIntoBitmap step 7");
                        ASSERT(result == JNI_OK, "Couldn't attach current thread to JVM.");
                        static_cast<void> (result);
                    }
                    {
                        const jint result{AndroidBitmap_unlockPixels(env, globalBitmap)};
                        ASSERT(result == JNI_OK, "Couldn't unlock the Android bitmap pixels.");
                        static_cast<void> (result);
                    }

                    env->DeleteGlobalRef(globalBitmap);
                    {
                        const jint result {
                            javaVM_->GetEnv(reinterpret_cast<void **> (const_cast<JNIEnv **> (&env)), JNI_VERSION_1_6)
                        };
                        ASSERT(result == JNI_OK, "JNIEnv not valid.");
                        static_cast<void> (result);
                    }
                    env->ExceptionClear();
                    {
                        const jint result {javaVM_->DetachCurrentThread()};
                        ASSERT(result == JNI_OK, "Couldn't detach the current thread from JVM.");
                        static_cast<void> (result);
                    }
                }
                const double renderingTime {timeRendering.count()};
                const ::std::uint64_t castedRays {renderer_->getTotalCastedRays()};
                LOG_INFO("Rendering Time in secs = ", renderingTime);
                LOG_INFO("Casted rays = ", castedRays);
                LOG_INFO("Total Millions rays per second = ", (static_cast<double> (castedRays) / renderingTime) / 1000000L);

                state_ = State::IDLE;
                LOG_DEBUG("rtRenderIntoBitmap finished");
            }
        };

        MobileRT::checkSystemError("rtRenderIntoBitmap creating thread");
        thread_ = ::MobileRT::std::make_unique<::std::thread>(lambda);
        if (errno == EINVAL) {
            // Ignore invalid argument (necessary for Android API 16)
            errno = 0;
        }
        MobileRT::checkSystemError("rtRenderIntoBitmap detaching thread");
        thread_->detach();

        LOG_DEBUG("rtRenderIntoBitmap finished preparing");
        MobileRT::checkSystemError("rtRenderIntoBitmap finish");
        env->ExceptionClear();
    } catch (const ::std::bad_alloc &badAlloc) {
        handleException(env, badAlloc, "puscas/mobilertapp/exceptions/LowMemoryException");
    } catch (const ::std::exception &exception) {
        handleException(env, exception, "java/lang/RuntimeException");
    } catch (...) {
        handleException(env, ::std::exception{}, "java/lang/RuntimeException");
    }
}

extern "C"
::std::int32_t Java_puscas_mobilertapp_RenderTask_rtGetState(
    JNIEnv *env,
    jobject /*thiz*/
) {
    MobileRT::checkSystemError("rtGetState start");

    const ::std::int32_t res {static_cast<::std::int32_t> (state_.load())};
    env->ExceptionClear();
    MobileRT::checkSystemError("rtGetState finish");
    return res;
}

extern "C"
float Java_puscas_mobilertapp_RenderTask_rtGetFps(
    JNIEnv *env,
    jobject /*thiz*/
) {
    if (errno == ETIMEDOUT || errno == EBADF) {
        // Ignore connection timed out
        // Ignore bad file descriptor (necessary for Android API 24)
        errno = 0;
    }
    MobileRT::checkSystemError("rtGetFps start");
    env->ExceptionClear();
    MobileRT::checkSystemError("rtGetFps finish");
    return fps_;
}

extern "C"
jlong Java_puscas_mobilertapp_RenderTask_rtGetTimeRenderer(
    JNIEnv *env,
    jobject /*thiz*/
) {
    MobileRT::checkSystemError("rtGetTimeRenderer start");
    env->ExceptionClear();
    MobileRT::checkSystemError("rtGetTimeRenderer finish");
    return timeRenderer_;
}

extern "C"
::std::int32_t Java_puscas_mobilertapp_RenderTask_rtGetSample(
    JNIEnv *env,
    jobject /*thiz*/
) {
    if (errno == EBADF) {
        // Ignore bad file descriptor (necessary for Android API 24)
        errno = 0;
    }
    MobileRT::checkSystemError("rtGetSample start");
    ::std::int32_t sample {};
    {
        const ::std::lock_guard<::std::mutex> lock {mutex_};
        if (renderer_ != nullptr) {
            sample = renderer_->getSample();
        }
    }
    env->ExceptionClear();
    MobileRT::checkSystemError("rtGetSample finish");
    return sample;
}

extern "C"
::std::int32_t Java_puscas_mobilertapp_MainActivity_rtResize(
    JNIEnv *env,
    jobject /*thiz*/,
    jint size
) {
    MobileRT::checkSystemError("rtResize start");

    const ::std::int32_t res{
        ::MobileRT::roundDownToMultipleOf(
            size, static_cast<::std::int32_t> (::std::sqrt(::MobileRT::NumberOfTiles))
        )
    };
    env->ExceptionClear();
    MobileRT::checkSystemError("rtResize finish");
    return res;
}

extern "C"
void Java_puscas_mobilertapp_MainActivity_resetErrno(
    JNIEnv *env,
    jclass /*thiz*/
) {
    errno = 0;
    MobileRT::checkSystemError("resetErrno start");
    env->ExceptionClear();
    MobileRT::checkSystemError("resetErrno finish");
}

extern "C"
JNIEXPORT
void JNICALL Java_puscas_mobilertapp_MainActivity_readFile(
        JNIEnv *env,
        jobject /*thiz*/,
        jint fileDescriptor,
        jlong fileSize,
        jstring jFilePath
) {
    if (errno == EACCES || errno == ENOTSOCK || errno == EPERM || errno == ENOENT) {
        // Ignore permission denied before reading the file.
        // Ignore socket operation on non-socket.
        // Ignore operation not permitted.
        // Ignore no such file or directory.
        errno = 0;
    }
    jboolean isCopy {JNI_FALSE};
    const ::std::string filePathRaw {env->GetStringUTFChars(jFilePath, &isCopy)};
    const ::std::string typeStr {filePathRaw.substr(filePathRaw.find_last_of("."), filePathRaw.size())};
    const int type {
          typeStr == ".obj" ? 0
        : typeStr == ".mtl" ? 1
        : typeStr == ".cam" ? 2
        : 3
    };
    LOG_DEBUG("Will read a file natively.");
    ::std::string *file {nullptr};
    switch (type) {
        case 0:
            file = &objDefinition_;
            break;

        case 1:
            file = &mtlDefinition_;
            break;

        case 2:
            file = &camDefinition_;
            break;

        default:
            file = nullptr;
    }

    ASSERT(fileDescriptor > 2, "File descriptor not valid.");
    ASSERT(fileSize > 0, "File size not valid.");

    if (file != nullptr) {
        LOG_DEBUG("Will read a scene file.");
        file->resize(static_cast<::std::size_t> (fileSize));
        MobileRT::checkSystemError("Before read file.");
        const long remainingLength {::read(fileDescriptor, &(*file)[0], static_cast<unsigned int>(fileSize))};
        MobileRT::checkSystemError("After read file.");
        ASSERT(remainingLength == 0 || remainingLength == fileSize, "File not read entirely.");
        LOG_DEBUG("Read a scene file.");
        static_cast<void>(remainingLength);
    } else {
        LOG_DEBUG("Will read a texture file.");
        ::std::string texture {};
        texture.resize(static_cast<::std::size_t> (fileSize));
        MobileRT::checkSystemError("Before read file.");
        const long remainingLength {::read(fileDescriptor, &texture[0], static_cast<unsigned int>(fileSize))};
        MobileRT::checkSystemError("After read file.");
        ASSERT(remainingLength == 0 || remainingLength == fileSize, "File not read entirely.");
        const ::std::string fileName {filePathRaw.substr(filePathRaw.find_last_of('/') + 1, filePathRaw.size())};
        ::Components::OBJLoader::getTextureFromCache(&texturesCache_, ::std::move(texture), static_cast<long> (fileSize), fileName);
        LOG_DEBUG("Read a texture file: ", filePathRaw);
        static_cast<void>(remainingLength);
    }
}

extern "C"
::std::int32_t Java_puscas_mobilertapp_DrawView_rtGetNumberOfLights(
    JNIEnv *env,
    jobject /*thiz*/
) {
    MobileRT::checkSystemError("rtGetNumberOfLights start");
    env->ExceptionClear();
    MobileRT::checkSystemError("rtGetNumberOfLights finish");
    return numLights_;
}

extern "C"
jobject Java_puscas_mobilertapp_MainRenderer_rtFreeNativeBuffer(
    JNIEnv *env,
    jobject /*thiz*/,
    jobject bufferRef
) {
    MobileRT::checkSystemError("rtFreeNativeBuffer start");
    if (bufferRef != nullptr) {
        void *buffer{env->GetDirectBufferAddress(bufferRef)};
        float *const floatBuffer{static_cast<float *> (buffer)};
        delete[] floatBuffer;
    }
    MobileRT::checkSystemError("rtFreeNativeBuffer finish");
    return nullptr;
}