mirror of
https://github.com/lighttransport/tinyusdz.git
synced 2026-01-18 01:11:17 +01:00
Display rendered image in Android example.
This commit is contained in:
BIN
android/app/src/main/assets/suzanne.usdc
Executable file
BIN
android/app/src/main/assets/suzanne.usdc
Executable file
Binary file not shown.
@@ -14,18 +14,33 @@ set(TINYUSDZ_DEP_SOURCES
|
||||
${PROJECT_SOURCE_DIR}/../../../../../src/external/ryu/ryu/s2f.c
|
||||
)
|
||||
|
||||
# Reuse files from sdlviewer
|
||||
set(USDVIEW_SOURCES
|
||||
${PROJECT_SOURCE_DIR}/../../../../../examples/sdlviewer/simple-render.cc
|
||||
${PROJECT_SOURCE_DIR}/../../../../../examples/common/matrix.cc
|
||||
${PROJECT_SOURCE_DIR}/../../../../../examples/common/trackball.cc
|
||||
)
|
||||
|
||||
# Build the libhello-oboe library
|
||||
add_library(hello-tinyusdz SHARED
|
||||
jni-tinyusdz.cc
|
||||
render-ctx.cc
|
||||
${TINYUSDZ_SOURCES}
|
||||
${TINYUSDZ_DEP_SOURCES}
|
||||
${USDVIEW_SOURCES}
|
||||
)
|
||||
|
||||
target_link_libraries(hello-tinyusdz jnigraphics android log)
|
||||
|
||||
target_include_directories(hello-tinyusdz PRIVATE
|
||||
${PROJECT_SOURCE_DIR}/../../../../../src/
|
||||
${PROJECT_SOURCE_DIR}/../../../../../src/external/ryu
|
||||
|
||||
# nanort, nanosg, etc
|
||||
${PROJECT_SOURCE_DIR}/../../../../../examples/common/
|
||||
|
||||
# sdlviewer example
|
||||
${PROJECT_SOURCE_DIR}/../../../../../examples/sdlviewer/
|
||||
)
|
||||
|
||||
# Required to load .usd files from Android asset for demo purpose
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
|
||||
#include "tinyusdz.hh"
|
||||
|
||||
// global
|
||||
tinyusdz::Scene g_scene;
|
||||
#include "render-ctx.hh"
|
||||
|
||||
#ifndef TINYUSDZ_ANDROID_LOAD_FROM_ASSETS
|
||||
#error "This demo requires to load .usd file from Android Assets"
|
||||
@@ -16,48 +15,108 @@ tinyusdz::Scene g_scene;
|
||||
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
// https://stackoverflow.com/questions/41820039/jstringjni-to-stdstringc-with-utf8-characters
|
||||
std::string jstring2string(JNIEnv *env, jstring jStr) {
|
||||
if (!jStr)
|
||||
return "";
|
||||
|
||||
const jclass stringClass = env->GetObjectClass(jStr);
|
||||
const jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B");
|
||||
const jbyteArray stringJbytes = (jbyteArray) env->CallObjectMethod(jStr, getBytes, env->NewStringUTF("UTF-8"));
|
||||
|
||||
size_t length = (size_t) env->GetArrayLength(stringJbytes);
|
||||
jbyte* pBytes = env->GetByteArrayElements(stringJbytes, NULL);
|
||||
|
||||
std::string ret = std::string((char *)pBytes, length);
|
||||
env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT);
|
||||
|
||||
env->DeleteLocalRef(stringJbytes);
|
||||
env->DeleteLocalRef(stringClass);
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_example_hellotinyusdz_MainActivity_updateImage(JNIEnv *env, jobject obj, jintArray _intarray, jint width, jint height) {
|
||||
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_example_hellotinyusdz_MainActivity_grabImage(JNIEnv *env, jobject obj, jintArray _intarray, jint width, jint height) {
|
||||
|
||||
__android_log_print(ANDROID_LOG_INFO, "tinyusdz", "grabImage");
|
||||
|
||||
jint *ptr = env->GetIntArrayElements(_intarray, NULL);
|
||||
int length = env->GetArrayLength(_intarray);
|
||||
if (length != (width * height)) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "tinyusdz", "Buffer size mismatch");
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t *buf = reinterpret_cast<uint32_t *>(ptr);
|
||||
for (size_t y = 0; y < height; y++) {
|
||||
for (size_t x = 0; x < width; x++) {
|
||||
uint8_t r = x % 255;
|
||||
uint8_t g = y % 255;
|
||||
uint8_t b = 128;
|
||||
uint8_t a = 255;
|
||||
// argb
|
||||
buf[y * width + x] = (a << 24) | (r << 16) | (g << 8) | (b);
|
||||
}
|
||||
if (length != (example::g_gui_ctx.aov.width * example::g_gui_ctx.aov.height)) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "tinyusdz", "AOV size mismatch");
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t *dest_ptr = reinterpret_cast<uint32_t *>(ptr);
|
||||
|
||||
std::vector<uint32_t> src;
|
||||
example::GetRenderedImage(example::g_gui_ctx, &src);
|
||||
|
||||
if (src.size() != length) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "tinyusdz", "GetRenderedImage failed.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(dest_ptr, src.data(), sizeof(uint32_t) * length);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_example_hellotinyusdz_MainActivity_renderImage(
|
||||
JNIEnv *env,
|
||||
jobject obj,
|
||||
jint width, jint height)
|
||||
{
|
||||
example::g_gui_ctx.render_width = width;
|
||||
example::g_gui_ctx.render_height = height;
|
||||
|
||||
__android_log_print(ANDROID_LOG_INFO, "tinyusdz", "renderImage");
|
||||
__android_log_print(ANDROID_LOG_INFO, "tinyusdz", "draw_meshes %d", (int)example::g_gui_ctx.render_scene.draw_meshes.size());
|
||||
bool ret = example::RenderScene(example::g_gui_ctx);
|
||||
|
||||
if (!ret) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "tinyusdz", "RenderScene failed.");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Returns: 0 - success
|
||||
* -1 - failed
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_example_hellotinyusdz_MainActivity_createStream(
|
||||
Java_com_example_hellotinyusdz_MainActivity_initScene(
|
||||
JNIEnv *env,
|
||||
jobject obj,
|
||||
jobject assetManager) {
|
||||
jobject assetManager,
|
||||
jstring _filename) {
|
||||
|
||||
std::string filename = jstring2string(env, _filename);
|
||||
tinyusdz::asset_manager = AAssetManager_fromJava(env, assetManager);
|
||||
|
||||
tinyusdz::USDLoadOptions options;
|
||||
|
||||
// load from Android asset folder
|
||||
example::g_gui_ctx.scene = tinyusdz::Scene(); // reset
|
||||
|
||||
std::string warn, err;
|
||||
bool ret = LoadUSDCFromFile("suzanne.usdc", &g_scene, &warn, &err, options);
|
||||
bool ret = LoadUSDCFromFile(filename, &example::g_gui_ctx.scene, &warn, &err, options);
|
||||
|
||||
if (warn.size()) {
|
||||
__android_log_print(ANDROID_LOG_WARN, "tinyusdz", "USD load warning: %s", warn.c_str());
|
||||
@@ -70,11 +129,16 @@ extern "C" {
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "tinyusdz", "USD loaded. #of geom_meshes: %d", int(g_scene.geom_meshes.size()));
|
||||
return int(g_scene.geom_meshes.size());
|
||||
__android_log_print(ANDROID_LOG_INFO, "tinyusdz", "USD loaded. #of geom_meshes: %d", int(example::g_gui_ctx.scene.geom_meshes.size()));
|
||||
}
|
||||
|
||||
// err
|
||||
return -1;
|
||||
ret = example::SetupScene(example::g_gui_ctx);
|
||||
if (!ret) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "tinyusdz", "SetupScene failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
return int(example::g_gui_ctx.scene.geom_meshes.size()); // OK
|
||||
}
|
||||
}
|
||||
|
||||
125
android/app/src/main/cpp/render-ctx.cc
Normal file
125
android/app/src/main/cpp/render-ctx.cc
Normal file
@@ -0,0 +1,125 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#include <android/log.h>
|
||||
|
||||
#include "render-ctx.hh"
|
||||
|
||||
namespace example {
|
||||
|
||||
GUIContext g_gui_ctx;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
// TODO: Use pow table for faster conversion.
|
||||
inline float linearToSrgb(float x) {
|
||||
if (x <= 0.0f)
|
||||
return 0.0f;
|
||||
else if (x >= 1.0f)
|
||||
return 1.0f;
|
||||
else if (x < 0.0031308f)
|
||||
return x * 12.92f;
|
||||
else
|
||||
return std::pow(x, 1.0f / 2.4f) * 1.055f - 0.055f;
|
||||
}
|
||||
|
||||
inline uint8_t ftouc(float f) {
|
||||
int val = int(f * 255.0f);
|
||||
val = std::max(0, std::min(255, val));
|
||||
|
||||
return static_cast<uint8_t>(val);
|
||||
}
|
||||
|
||||
inline double radians(double degree) { return 3.141592653589 * degree / 180.0; }
|
||||
|
||||
// https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
|
||||
std::array<double, 4> ToQuaternion(double yaw, double pitch,
|
||||
double roll) // yaw (Z), pitch (Y), roll (X)
|
||||
{
|
||||
// Abbreviations for the various angular functions
|
||||
double cy = std::cos(yaw * 0.5);
|
||||
double sy = std::sin(yaw * 0.5);
|
||||
double cp = std::cos(pitch * 0.5);
|
||||
double sp = std::sin(pitch * 0.5);
|
||||
double cr = std::cos(roll * 0.5);
|
||||
double sr = std::sin(roll * 0.5);
|
||||
|
||||
std::array<double, 4> q;
|
||||
q[0] = cr * cp * cy + sr * sp * sy;
|
||||
q[1] = sr * cp * cy - cr * sp * sy;
|
||||
q[2] = cr * sp * cy + sr * cp * sy;
|
||||
q[3] = cr * cp * sy - sr * sp * cy;
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool SetupScene(GUIContext &ctx) {
|
||||
|
||||
__android_log_print(ANDROID_LOG_INFO, "tinyusdz", "SetupScene");
|
||||
if (ctx.scene.geom_meshes.empty()) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "tinyusdz", "No GeomMesh");
|
||||
// No GeomMesh in the scene
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert USD geom_mesh to renderable mesh.
|
||||
ctx.render_scene.draw_meshes.clear();
|
||||
__android_log_print(ANDROID_LOG_INFO, "tinyusdz", "# of geom_meshes %d", (int)ctx.scene.geom_meshes.size());
|
||||
for (size_t i = 0; i < ctx.scene.geom_meshes.size(); i++) {
|
||||
example::DrawGeomMesh draw_mesh(&ctx.scene.geom_meshes[i]);
|
||||
ctx.render_scene.draw_meshes.push_back(draw_mesh);
|
||||
}
|
||||
|
||||
// Setup render mesh
|
||||
if (!ctx.render_scene.Setup()) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "tinyusdz", "Scene::Setup failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// init camera matrix
|
||||
{
|
||||
auto q = ToQuaternion(radians(ctx.yaw), radians(ctx.pitch),
|
||||
radians(ctx.roll));
|
||||
ctx.camera.quat[0] = q[0];
|
||||
ctx.camera.quat[1] = q[1];
|
||||
ctx.camera.quat[2] = q[2];
|
||||
ctx.camera.quat[3] = q[3];
|
||||
}
|
||||
|
||||
// HACK. camera position adjusted for `suzanne.usdc`
|
||||
ctx.camera.eye[2] = 3.5f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderScene(GUIContext &ctx)
|
||||
{
|
||||
// Init AOV image
|
||||
ctx.aov.Resize(ctx.render_width, ctx.render_height);
|
||||
|
||||
example::Render(ctx.render_scene, ctx.camera, &ctx.aov);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GetRenderedImage(const GUIContext &ctx, std::vector<uint32_t> *argb) {
|
||||
if (!argb) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> buf;
|
||||
buf.resize(ctx.aov.width * ctx.aov.height);
|
||||
|
||||
for (size_t i = 0; i < ctx.aov.width * ctx.aov.height; i++) {
|
||||
uint8_t r = ftouc(linearToSrgb(ctx.aov.rgb[3 * i + 0]));
|
||||
uint8_t g = ftouc(linearToSrgb(ctx.aov.rgb[3 * i + 1]));
|
||||
uint8_t b = ftouc(linearToSrgb(ctx.aov.rgb[3 * i + 2]));
|
||||
uint8_t a = 255;
|
||||
|
||||
buf[i] = (a << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
(*argb) = buf;
|
||||
}
|
||||
} // example
|
||||
82
android/app/src/main/cpp/render-ctx.hh
Normal file
82
android/app/src/main/cpp/render-ctx.hh
Normal file
@@ -0,0 +1,82 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include "simple-render.hh"
|
||||
|
||||
namespace example {
|
||||
|
||||
struct GUIContext {
|
||||
enum AOVMode {
|
||||
AOV_COLOR = 0,
|
||||
AOV_SHADING_NORMAL,
|
||||
AOV_GEOMETRIC_NORMAL,
|
||||
AOV_POSITION,
|
||||
AOV_DEPTH,
|
||||
AOV_TEXCOORD,
|
||||
AOV_VARYCOORD,
|
||||
AOV_VERTEXCOLOR
|
||||
};
|
||||
int aov_mode{AOV_COLOR};
|
||||
|
||||
|
||||
example::AOV aov; // framebuffer
|
||||
|
||||
int width = 1024;
|
||||
int height = 768;
|
||||
|
||||
int mouse_x = -1;
|
||||
int mouse_y = -1;
|
||||
|
||||
bool mouse_left_down = false;
|
||||
bool shift_pressed = false;
|
||||
bool ctrl_pressed = false;
|
||||
bool tab_pressed = false;
|
||||
|
||||
float yaw = 90.0f; // for Z up scene
|
||||
float pitch = 0.0f;
|
||||
float roll = 0.0f;
|
||||
|
||||
// float curr_quat[4] = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
// std::array<float, 4> prev_quat[4] = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
|
||||
// std::array<float, 3> eye = {0.0f, 0.0f, 5.0f};
|
||||
// std::array<float, 3> lookat = {0.0f, 0.0f, 0.0f};
|
||||
// std::array<float, 3> up = {0.0f, 1.0f, 0.0f};
|
||||
|
||||
example::RenderScene render_scene;
|
||||
|
||||
example::Camera camera;
|
||||
|
||||
std::atomic<bool> update_texture{false};
|
||||
std::atomic<bool> redraw{true}; // require redraw
|
||||
std::atomic<bool> quit{false};
|
||||
|
||||
int render_width = 512;
|
||||
int render_height = 512;
|
||||
|
||||
// scene reload
|
||||
tinyusdz::Scene scene;
|
||||
std::atomic<bool> request_reload{false};
|
||||
std::string filename;
|
||||
|
||||
#if __EMSCRIPTEN__ || defined(EMULATE_EMSCRIPTEN)
|
||||
bool render_finished{false};
|
||||
int current_render_line = 0;
|
||||
int render_line_size = 32; // render images with this lines per animation loop.
|
||||
// for emscripten environment
|
||||
#endif
|
||||
};
|
||||
|
||||
extern GUIContext g_gui_ctx;
|
||||
|
||||
|
||||
// Setup scene. Only need to call once USD scene(ctx.scene) is loaded.
|
||||
bool SetupScene(GUIContext &ctx);
|
||||
|
||||
// Render the scene(and store rendered image to ctx.aov)
|
||||
bool RenderScene(GUIContext &ctx);
|
||||
|
||||
// Get rendered image from ctx.aov as Android ARGB_8888 IntArray.
|
||||
void GetRenderedImage(const GUIContext &ctx, std::vector<uint32_t> *argb);
|
||||
|
||||
} // example
|
||||
@@ -31,34 +31,31 @@ import kotlinx.android.synthetic.main.activity_main.sample_text
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
val render_width = 512;
|
||||
val render_height = 512;
|
||||
|
||||
fun updateRender() {
|
||||
renderImage(render_width, render_height);
|
||||
|
||||
var conf = Bitmap.Config.ARGB_8888
|
||||
var b = Bitmap.createBitmap(render_width, render_height, conf)
|
||||
|
||||
var pixels = IntArray(render_width * render_height)
|
||||
|
||||
grabImage(pixels, render_width, render_height)
|
||||
|
||||
b.setPixels(pixels, 0, render_width, 0, 0, render_width, render_height)
|
||||
|
||||
var img = findViewById<ImageView>(R.id.imageView)
|
||||
|
||||
img.setImageBitmap(b)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
val view = findViewById<View>(R.id.container)
|
||||
|
||||
var conf = Bitmap.Config.ARGB_8888
|
||||
var b = Bitmap.createBitmap(512, 512, conf)
|
||||
|
||||
var pixels = IntArray(512 * 512)
|
||||
|
||||
//b.getPixels(pixels, 0,512, 0, 0, 512, 512)
|
||||
|
||||
//for (y in 0 until 512) {
|
||||
// for (x in 0 until 512) {
|
||||
// pixels[y * 512 + x] = Color.argb(125, x % 256, y % 256, 64)
|
||||
// }
|
||||
//}
|
||||
|
||||
var width = 512
|
||||
var height = 512
|
||||
|
||||
updateImage(pixels, width, height)
|
||||
|
||||
b.setPixels(pixels, 0, 512, 0, 0, 512, 512)
|
||||
|
||||
var img = findViewById<ImageView>(R.id.imageView)
|
||||
|
||||
img.setImageBitmap(b)
|
||||
|
||||
// Set up a touch listener which calls the native sound engine
|
||||
view.setOnTouchListener {_, event ->
|
||||
@@ -76,7 +73,7 @@ class MainActivity : AppCompatActivity() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
var n = createStream(getAssets());
|
||||
var n = initScene(getAssets(), "suzanne.usdc");
|
||||
|
||||
if (n <= 0) {
|
||||
val errorString : String = "Failed to load USD file"
|
||||
@@ -87,6 +84,7 @@ class MainActivity : AppCompatActivity() {
|
||||
Toast.makeText(applicationContext, s,Toast.LENGTH_LONG).show()
|
||||
sample_text.text = s
|
||||
|
||||
updateRender()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,9 +93,9 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
|
||||
// Creates and starts Oboe stream to play audio
|
||||
private external fun createStream(mgr: AssetManager) : Int
|
||||
private external fun updateImage(img: IntArray, width: Int, height: Int) : Int
|
||||
private external fun initScene(mgr: AssetManager, filename: String) : Int
|
||||
private external fun renderImage(width: Int, height: Int) : Int
|
||||
private external fun grabImage(img: IntArray, width: Int, height: Int) : Int
|
||||
|
||||
companion object {
|
||||
// Used to load native code calling oboe on app startup.
|
||||
|
||||
@@ -346,7 +346,7 @@ bool Render(const RenderScene& scene, const Camera& cam, AOV* output) {
|
||||
// auto startT = std::chrono::system_clock::now();
|
||||
|
||||
for (auto t = 0; t < num_threads; t++) {
|
||||
workers.emplace_back(std::thread([&, t]() {
|
||||
workers.emplace_back(std::thread([&]() {
|
||||
int y = 0;
|
||||
while ((y = i++) < height) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
@@ -371,8 +371,13 @@ bool Render(const RenderScene& scene, const Camera& cam, AOV* output) {
|
||||
|
||||
bool hit = false;
|
||||
|
||||
output->rgb[3 * pixel_idx + 0] = 0.0f;
|
||||
output->rgb[3 * pixel_idx + 1] = 0.0f;
|
||||
output->rgb[3 * pixel_idx + 2] = 0.0f;
|
||||
|
||||
|
||||
if (scene.draw_meshes.size()) {
|
||||
|
||||
// HACK. Use the first mesh
|
||||
const DrawGeomMesh& mesh = scene.draw_meshes[0];
|
||||
|
||||
@@ -472,13 +477,11 @@ bool Render(const RenderScene& scene, const Camera& cam, AOV* output) {
|
||||
output->texcoords[2 * pixel_idx + 0] = texcoord[0];
|
||||
output->texcoords[2 * pixel_idx + 1] = texcoord[1];
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if (!hit) {
|
||||
|
||||
output->rgb[3 * pixel_idx + 0] = 0.0f;
|
||||
output->rgb[3 * pixel_idx + 1] = 0.0f;
|
||||
output->rgb[3 * pixel_idx + 2] = 0.0f;
|
||||
|
||||
output->geometric_normal[3 * pixel_idx + 0] = 0.0f;
|
||||
output->geometric_normal[3 * pixel_idx + 1] = 0.0f;
|
||||
|
||||
Reference in New Issue
Block a user