mirror of
https://github.com/amwatson/CitraVR.git
synced 2024-09-20 03:11:40 +02:00
add VRUILayer [java] class
This commit is contained in:
parent
12429f1ab5
commit
043f57ec8c
4 changed files with 193 additions and 0 deletions
1
src/android/app/proguard-rules.pro
vendored
1
src/android/app/proguard-rules.pro
vendored
|
@ -25,3 +25,4 @@
|
|||
-dontwarn java.beans.VetoableChangeSupport
|
||||
-keep class org.citra.citra_emu.vr.GameSurfaceLayer { *; }
|
||||
-keep class org.citra.citra_emu.vr.VrActivity { *; }
|
||||
-keep class org.citra.citra_emu.vr.ui.VrUILayer { *; }
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
package org.citra.citra_emu.vr.ui
|
||||
|
||||
import android.app.Presentation
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.hardware.display.VirtualDisplay
|
||||
import android.os.SystemClock
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.InputDevice
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.MotionEvent.PointerCoords
|
||||
import android.view.MotionEvent.PointerProperties
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.vr.VrActivity
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/*
|
||||
* This class populates an "SwapchainAndroidSurfaceKHR" with the
|
||||
* contents of a secondary virtual display. It allows for smooth animations,
|
||||
* but the perf doesn't scale well with texture size and it doesn't support mip
|
||||
*levels. Therefore, it is important to set the the display size of the texture
|
||||
*(using resource sizes, display density and the native density constant) to be
|
||||
*something that's close enough to 1:1 texels:pixels so as to not require mips.
|
||||
**/
|
||||
abstract class VrUILayer(
|
||||
val activity: VrActivity,
|
||||
densityDpi: Int = DEFAULT_DENSITY.toInt(),
|
||||
private val layoutId: Int = R.layout.vr_keyboard
|
||||
) {
|
||||
private val requestedDensity: Float = densityDpi.toFloat()
|
||||
private var virtualDisplay: VirtualDisplay? = null
|
||||
private var presentation: Presentation? = null
|
||||
|
||||
val window: Window?
|
||||
get() = presentation!!.window
|
||||
|
||||
/// Called from JNI ////
|
||||
fun getBoundsForView(handle: Long): Int {
|
||||
val inflater = activity.getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE
|
||||
) as LayoutInflater
|
||||
val contentView = inflater.inflate(layoutId, null, false)
|
||||
if (contentView == null) {
|
||||
Log.error( "Failed to inflate content view")
|
||||
return -1
|
||||
}
|
||||
|
||||
contentView.measure(
|
||||
View.MeasureSpec.UNSPECIFIED,
|
||||
View.MeasureSpec.UNSPECIFIED
|
||||
)
|
||||
|
||||
val displayMetrics = activity.resources.displayMetrics
|
||||
val widthDp = contentView.measuredWidth.toFloat() / displayMetrics.density /
|
||||
(DEFAULT_DENSITY / requestedDensity)
|
||||
val heightDp = contentView.measuredHeight.toFloat() / displayMetrics.density /
|
||||
(DEFAULT_DENSITY / requestedDensity)
|
||||
// roundToInt() matching the rounding Android uses to convert view dimensions to display units
|
||||
nativeSetBounds(
|
||||
handle, 0, 0, widthDp.roundToInt(),
|
||||
heightDp.roundToInt()
|
||||
)
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun dispatchTouchEvent(x: Float, y: Float, action: Int) {
|
||||
val eventTime = SystemClock.uptimeMillis()
|
||||
MotionEvent.obtain(
|
||||
eventTime, // Use the same timestamp for both downTime and eventTime
|
||||
eventTime,
|
||||
action,
|
||||
1, // Only one pointer is used here
|
||||
arrayOf(PointerProperties().apply {
|
||||
id = 0
|
||||
toolType = MotionEvent.TOOL_TYPE_FINGER
|
||||
}),
|
||||
arrayOf(PointerCoords().apply {
|
||||
this.x = x
|
||||
this.y = y
|
||||
pressure = 1f
|
||||
size = 1f
|
||||
}),
|
||||
0, 0, // MetaState and buttonState
|
||||
1f, 1f, // Precision X and Y
|
||||
0, 0, // Device ID and Edge Flags
|
||||
InputDevice.SOURCE_TOUCHSCREEN,
|
||||
0 // Flags
|
||||
).apply {
|
||||
presentation?.window?.decorView?.dispatchTouchEvent(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun sendClickToUI(x: Float, y: Float, motionType: Int): Int {
|
||||
val action = when (motionType) {
|
||||
0 -> MotionEvent.ACTION_UP
|
||||
1 -> MotionEvent.ACTION_DOWN
|
||||
2 -> MotionEvent.ACTION_MOVE
|
||||
else -> MotionEvent.ACTION_HOVER_ENTER
|
||||
}
|
||||
activity.runOnUiThread { dispatchTouchEvent(x, y, action) }
|
||||
return 0
|
||||
}
|
||||
|
||||
fun setSurface(
|
||||
surface: Surface, widthDp: Int,
|
||||
heightDp: Int
|
||||
): Int {
|
||||
activity.runOnUiThread { setSurface_(surface, widthDp, heightDp) }
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun setSurface_(
|
||||
surface: Surface, widthDp: Int,
|
||||
heightDp: Int
|
||||
) {
|
||||
val flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION
|
||||
val displayManager = activity.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
virtualDisplay = displayManager.createVirtualDisplay(
|
||||
"CitraVR", widthDp, heightDp, requestedDensity.toInt(), surface,
|
||||
flags
|
||||
)
|
||||
presentation = Presentation(activity.applicationContext, virtualDisplay!!.display).apply {
|
||||
window?.setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION)
|
||||
setContentView(layoutId)
|
||||
// Sets the background to transparent. Remove to set background to black
|
||||
// (useful for catching overrendering)
|
||||
window?.setBackgroundDrawable(ColorDrawable(0))
|
||||
show()
|
||||
}
|
||||
onSurfaceCreated()
|
||||
}
|
||||
|
||||
protected fun onSurfaceCreated() {}
|
||||
|
||||
fun writeBitmapToDisk(bmp: Bitmap, outName: String?) {
|
||||
val sdCard = activity.externalCacheDir
|
||||
if (sdCard != null && outName != null) {
|
||||
val file = File(sdCard.absolutePath, outName)
|
||||
try {
|
||||
FileOutputStream(file).use { out ->
|
||||
bmp.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.error("Failed to write bitmap to disk: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// DPI android uses as "1:1" with dp coordinates.
|
||||
// AKA "baseline density"
|
||||
private const val DEFAULT_DENSITY = DisplayMetrics.DENSITY_MEDIUM.toFloat()
|
||||
private external fun nativeSetBounds(
|
||||
handle: Long, leftInDp: Int, topInDp: Int,
|
||||
rightInDp: Int, bottomInDp: Int
|
||||
)
|
||||
|
||||
/*** Debug/Testing */
|
||||
const val DEBUG_WRITE_VIEW_TO_DISK = false
|
||||
}
|
||||
}
|
|
@ -96,6 +96,20 @@ struct BoundsHandle
|
|||
};
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// JNI functions
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_citra_citra_1emu_vr_ui_UILayer_00024Companion_nativeSetBounds(
|
||||
JNIEnv* env, jobject thiz, jlong handle, jint left, jint top, jint right,
|
||||
jint bottom)
|
||||
{
|
||||
AndroidWindowBounds* b = BoundsHandle(handle).p;
|
||||
b->mLeftInDp = left;
|
||||
b->mTopInDp = top;
|
||||
b->mRightInDp = right;
|
||||
b->mBottomInDp = bottom;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Local sysprops
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ License : Licensed under GPLv3 or any later version.
|
|||
#include "layers/CursorLayer.h"
|
||||
#include "layers/GameSurfaceLayer.h"
|
||||
#include "layers/PassthroughLayer.h"
|
||||
#include "layers/UILayer.h"
|
||||
#include "vr_settings.h"
|
||||
|
||||
#include "utils/Common.h"
|
||||
|
@ -277,6 +278,11 @@ public:
|
|||
jni, mActivityObject, gOpenXr->mSession);
|
||||
#endif
|
||||
|
||||
|
||||
mUILayer = std::make_unique<UILayer>(
|
||||
"org/citra/citra_emu/vr/ui/VrUILayer",
|
||||
XrVector3f{0, 0.0f, -1.5f}, jni, mActivityObject, gOpenXr->mSession);
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Intialize JNI methods
|
||||
//////////////////////////////////////////////////
|
||||
|
@ -1036,6 +1042,7 @@ private:
|
|||
#endif
|
||||
std::unique_ptr<GameSurfaceLayer> mGameSurfaceLayer;
|
||||
std::unique_ptr<PassthroughLayer> mPassthroughLayer;
|
||||
std::unique_ptr<UILayer> mUILayer;
|
||||
|
||||
std::unique_ptr<InputStateStatic> mInputStateStatic;
|
||||
InputStateFrame mInputStateFrame;
|
||||
|
|
Loading…
Reference in a new issue