Initial commit

This commit is contained in:
2026-08-11 19:46:54 +08:00
commit 8d4535ff00
60 changed files with 2794 additions and 0 deletions
@@ -0,0 +1,186 @@
package com.example.jnicpp;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
import com.example.jnicpp.bowling.BowlingCameraActivity;
import com.example.jnicpp.databinding.ActivityMainBinding;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class MainActivity extends AppCompatActivity implements GLSurfaceView.Renderer {
private static final String TAG = "MainActivity";
// Used to load the 'jnicpp' library on application startup.
static {
try {
System.loadLibrary("jnicpp");
Log.d(TAG, "Native library loaded successfully");
} catch (UnsatisfiedLinkError e) {
Log.e(TAG, "Failed to load native library", e);
}
}
private ActivityMainBinding binding;
private GLSurfaceView glSurfaceView;
private boolean isInitialized = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate called");
try {
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Setup GLSurfaceView
glSurfaceView = binding.glSurfaceView;
glSurfaceView.setEGLContextClientVersion(3); // OpenGL ES 3.0
// Without this, launching an opaque Activity on top (e.g.
// BowlingCameraActivity) destroys the GLSurfaceView's Surface, which tears
// down the EGL context too. On return, a new context gets created and
// initGL() runs again, but UIRenderer's one-time GL object setup
// (UI::Init()'s s_initialized guard) doesn't know the old context - and its
// GL handles - are gone, so it skips recreating them and the menu UI stops
// drawing (leaving only the background demo triangle visible). Preserving
// the context avoids that teardown/recreate cycle entirely.
glSurfaceView.setPreserveEGLContextOnPause(true);
glSurfaceView.setRenderer(this);
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
// Touch listener attached directly to the GLSurfaceView (rather than
// overriding Activity.onTouchEvent) so MotionEvent.getX()/getY() are
// guaranteed relative to this view's own bounds - the same pixel space
// our GL rendering and UI hit-testing use. Activity-level touch
// coordinates aren't guaranteed to line up with a child view's
// coordinates in every layout, which was causing clicks to land on the
// wrong button.
glSurfaceView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getActionMasked();
boolean isDown = (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE);
nativeOnTouch(event.getX(), event.getY(), isDown);
return true;
}
});
// Example of a call to a native method
String message = stringFromJNI();
Log.d(TAG, "Native message: " + message);
} catch (Exception e) {
Log.e(TAG, "Error in onCreate", e);
showToast("Error initializing app: " + e.getMessage());
}
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause called");
if (glSurfaceView != null) {
glSurfaceView.onPause();
}
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume called");
if (glSurfaceView != null) {
glSurfaceView.onResume();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy called");
if (isInitialized) {
try {
cleanupGL();
} catch (Exception e) {
Log.e(TAG, "Error during cleanup", e);
}
}
}
// Helper method to show toast from any thread
private void showToast(final String message) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
}
});
}
/**
* Called from native code (see PlatformBridge::LaunchBowlingCamera() in
* my_gl_app/PlatformBridge.cpp) when the game state machine switches
* into StateID::Menu3. Invoked via JNI from the GL render thread, so
* this hops to the UI thread before touching Activity APIs, same as
* showToast() above. Front/back camera switching and backing out happen
* on BowlingCameraActivity itself (its SWITCH CAMERA and BACK buttons).
*/
public void launchBowlingCamera() {
runOnUiThread(new Runnable() {
@Override
public void run() {
startActivity(new Intent(MainActivity.this, BowlingCameraActivity.class));
}
});
}
// GLSurfaceView.Renderer implementation
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Log.d(TAG, "Surface created");
try {
// Initialize OpenGL in native code (no longer needs surface parameter)
boolean success = initGL();
if (success) {
isInitialized = true;
Log.d(TAG, "OpenGL ES 3.0 initialized successfully");
} else {
Log.e(TAG, "Failed to initialize OpenGL ES 3.0");
showToast("Failed to initialize OpenGL ES 3.0");
}
} catch (Exception e) {
Log.e(TAG, "Error in onSurfaceCreated", e);
showToast("Error creating surface: " + e.getMessage());
}
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
Log.d(TAG, "Surface changed: " + width + "x" + height);
onSurfaceResized(width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
if (isInitialized) {
try {
renderFrame();
} catch (Exception e) {
Log.e(TAG, "Error in onDrawFrame", e);
}
}
}
/**
* A native method that is implemented by the 'jnicpp' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
/**
* Initialize OpenGL ES 3.0 renderer
*/
public native boolean initGL();
/**
* Render a frame
*/
public native void renderFrame();
/**
* Cleanup OpenGL resources
*/
public native void cleanupGL();
/**
* Notify native code of the surface size, for UI layout/hit-testing
*/
public native void onSurfaceResized(int width, int height);
/**
* Forward a touch event to the native UI system
*/
public native void nativeOnTouch(float x, float y, boolean isDown);
}