android: Use OpenGLES2 for rendering the screen instead of ImageView
Former-commit-id: 80d96928dabaa215d0b8812e203d65193ac85cc8
This commit is contained in:
parent
b270265694
commit
46287ca88d
|
@ -2,6 +2,7 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.mrmichel.rustdroid_emu">
|
package="com.mrmichel.rustdroid_emu">
|
||||||
|
|
||||||
|
<uses-feature android:glEsVersion="0x00200000" android:required="true"></uses-feature>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ import android.view.MenuItem;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.Switch;
|
import android.widget.Switch;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
@ -53,8 +52,8 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL
|
||||||
private AudioThread audioThread;
|
private AudioThread audioThread;
|
||||||
private AudioTrack audioTrack;
|
private AudioTrack audioTrack;
|
||||||
private byte[] on_resume_saved_state = null;
|
private byte[] on_resume_saved_state = null;
|
||||||
private ImageView screen;
|
|
||||||
private boolean turboMode = false;
|
private boolean turboMode = false;
|
||||||
|
private GbaScreenView gbaScreenView;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
@ -233,7 +232,6 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL
|
||||||
|
|
||||||
|
|
||||||
this.bios = getIntent().getByteArrayExtra("bios");
|
this.bios = getIntent().getByteArrayExtra("bios");
|
||||||
this.screen = findViewById(R.id.gbaMockImageView);
|
|
||||||
this.emulator = new Emulator();
|
this.emulator = new Emulator();
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
|
@ -260,6 +258,8 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL
|
||||||
AudioTrack.MODE_STREAM);
|
AudioTrack.MODE_STREAM);
|
||||||
}
|
}
|
||||||
this.audioTrack.play();
|
this.audioTrack.play();
|
||||||
|
|
||||||
|
this.gbaScreenView = findViewById(R.id.gba_view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -336,10 +336,6 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL
|
||||||
startActivityForResult(intent, LOAD_SNAPSHOT_REQUESTCODE);
|
startActivityForResult(intent, LOAD_SNAPSHOT_REQUESTCODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateScreen(Bitmap bmp) {
|
|
||||||
this.screen.setImageBitmap(bmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class EmulationRunnable implements Runnable {
|
private class EmulationRunnable implements Runnable {
|
||||||
|
|
||||||
public static final long NANOSECONDS_PER_MILLISECOND = 1000000;
|
public static final long NANOSECONDS_PER_MILLISECOND = 1000000;
|
||||||
|
@ -373,14 +369,7 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emulatorActivity.runOnUiThread(new Runnable() {
|
emulatorActivity.gbaScreenView.updateFrame(emulator.getFrameBuffer());
|
||||||
Bitmap bitmap = Bitmap.createBitmap(emulator.getFrameBuffer(), 240, 160, Bitmap.Config.RGB_565);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
emulatorActivity.updateScreen(bitmap);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pauseEmulation() {
|
public void pauseEmulation() {
|
||||||
|
|
|
@ -0,0 +1,240 @@
|
||||||
|
package com.mrmichel.rustdroid_emu.ui;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.opengl.GLES20;
|
||||||
|
import android.opengl.GLSurfaceView;
|
||||||
|
import android.opengl.GLUtils;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.FloatBuffer;
|
||||||
|
|
||||||
|
import javax.microedition.khronos.egl.EGLConfig;
|
||||||
|
import javax.microedition.khronos.opengles.GL10;
|
||||||
|
|
||||||
|
public class GbaScreenView extends GLSurfaceView implements GLSurfaceView.Renderer {
|
||||||
|
ScreenTexture texture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private class to manage the screen texture rendering
|
||||||
|
*/
|
||||||
|
private class ScreenTexture {
|
||||||
|
int shaderProgram;
|
||||||
|
int positionHandle;
|
||||||
|
int texCoordHandle;
|
||||||
|
int samplerHandle;
|
||||||
|
int textureId;
|
||||||
|
|
||||||
|
private FloatBuffer vertexBuffer;
|
||||||
|
private FloatBuffer textureBuffer;
|
||||||
|
private ByteBuffer indicesBuffer;
|
||||||
|
|
||||||
|
private Bitmap bitmap;
|
||||||
|
|
||||||
|
// square vertices
|
||||||
|
private float[] vertices = {
|
||||||
|
-1.0f, 1.0f, 0.0f, // top left
|
||||||
|
-1.0f, -1.0f, 0.0f, // bottom left
|
||||||
|
1.0f, -1.0f, 0.0f, // bottom right
|
||||||
|
1.0f, 1.0f, 0.0f, // top right
|
||||||
|
};
|
||||||
|
|
||||||
|
// texture space vertices
|
||||||
|
private float[] textureVertices = {
|
||||||
|
0.0f, 0.0f,
|
||||||
|
0.0f, 1.0f,
|
||||||
|
1.0f, 1.0f,
|
||||||
|
1.0f, 0.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
// two triangles compose a rect
|
||||||
|
private byte[] indicies = {
|
||||||
|
0, 1, 2,
|
||||||
|
0, 2, 3
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final String VERTEX_SHADER_CODE =
|
||||||
|
"attribute vec4 a_position; \n" +
|
||||||
|
"attribute vec2 a_texCoord; \n" +
|
||||||
|
"varying vec2 v_texCoord; \n" +
|
||||||
|
"void main() \n" +
|
||||||
|
"{ \n" +
|
||||||
|
" gl_Position = a_position; \n" +
|
||||||
|
" v_texCoord = a_texCoord; \n" +
|
||||||
|
"} \n";
|
||||||
|
|
||||||
|
private static final String FRAGMENT_SHADER_CODE =
|
||||||
|
"precision mediump float; \n" +
|
||||||
|
"varying vec2 v_texCoord; \n" +
|
||||||
|
"uniform sampler2D s_texture; \n" +
|
||||||
|
"void main() \n" +
|
||||||
|
"{ \n" +
|
||||||
|
" gl_FragColor = texture2D( s_texture, v_texCoord );\n" +
|
||||||
|
"} \n";
|
||||||
|
|
||||||
|
|
||||||
|
private int compileShader(int type, String code) {
|
||||||
|
int shader = GLES20.glCreateShader(type);
|
||||||
|
GLES20.glShaderSource(shader, code);
|
||||||
|
GLES20.glCompileShader(shader);
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update(int[] frameBuffer) {
|
||||||
|
bitmap.setPixels(frameBuffer, 0, 240, 0, 0, 240, 160);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int createShaderProgram(String vertexShaderCode, String fragmentShaderCode) {
|
||||||
|
int vertexShader = compileShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
|
||||||
|
int fragmentShader = compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
|
||||||
|
|
||||||
|
int program = GLES20.glCreateProgram();
|
||||||
|
GLES20.glAttachShader(program, vertexShader);
|
||||||
|
GLES20.glAttachShader(program, fragmentShader);
|
||||||
|
|
||||||
|
GLES20.glLinkProgram(program);
|
||||||
|
|
||||||
|
return program;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int createTexture() {
|
||||||
|
int[] texturesIds = new int[1];
|
||||||
|
|
||||||
|
GLES20.glGenTextures(1, texturesIds, 0);
|
||||||
|
if (texturesIds[0] == GLES20.GL_FALSE) {
|
||||||
|
throw new RuntimeException("Error loading texture");
|
||||||
|
}
|
||||||
|
// bind the texture
|
||||||
|
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texturesIds[0]);
|
||||||
|
|
||||||
|
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
|
||||||
|
|
||||||
|
// set the parameters
|
||||||
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
|
||||||
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
|
||||||
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
||||||
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
return texturesIds[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScreenTexture() {
|
||||||
|
this.bitmap = Bitmap.createBitmap(240, 160, Bitmap.Config.RGB_565);
|
||||||
|
|
||||||
|
GLES20.glEnable(GLES20.GL_TEXTURE_2D);
|
||||||
|
|
||||||
|
// create vertex array
|
||||||
|
vertexBuffer = ByteBuffer.allocateDirect(vertices.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
|
||||||
|
vertexBuffer.put(vertices);
|
||||||
|
vertexBuffer.position(0);
|
||||||
|
|
||||||
|
// create texture coordinate array
|
||||||
|
textureBuffer = ByteBuffer.allocateDirect(textureVertices.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
|
||||||
|
textureBuffer.put(textureVertices);
|
||||||
|
textureBuffer.position(0);
|
||||||
|
|
||||||
|
// create triangle index array
|
||||||
|
indicesBuffer = ByteBuffer.allocateDirect(indicies.length).order(ByteOrder.nativeOrder());
|
||||||
|
indicesBuffer.put(indicies);
|
||||||
|
indicesBuffer.position(0);
|
||||||
|
|
||||||
|
textureId = createTexture();
|
||||||
|
|
||||||
|
shaderProgram = createShaderProgram(VERTEX_SHADER_CODE, FRAGMENT_SHADER_CODE);
|
||||||
|
|
||||||
|
// use the program
|
||||||
|
GLES20.glUseProgram(shaderProgram);
|
||||||
|
|
||||||
|
positionHandle = GLES20.glGetAttribLocation(shaderProgram, "a_position");
|
||||||
|
|
||||||
|
texCoordHandle = GLES20.glGetAttribLocation(shaderProgram, "a_texCoord");
|
||||||
|
|
||||||
|
samplerHandle = GLES20.glGetUniformLocation(shaderProgram, "s_texture");
|
||||||
|
|
||||||
|
|
||||||
|
// load the vertex position
|
||||||
|
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);
|
||||||
|
GLES20.glEnableVertexAttribArray(positionHandle);
|
||||||
|
// load texture coordinate
|
||||||
|
GLES20.glVertexAttribPointer(texCoordHandle, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
|
||||||
|
GLES20.glEnableVertexAttribArray(texCoordHandle);
|
||||||
|
|
||||||
|
|
||||||
|
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void destroy(){
|
||||||
|
GLES20.glDeleteProgram(shaderProgram);
|
||||||
|
int[] textures = {textureId};
|
||||||
|
GLES20.glDeleteTextures(1, textures, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void render() {
|
||||||
|
// clear the color buffer
|
||||||
|
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
// bind the texture
|
||||||
|
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
|
||||||
|
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
|
||||||
|
|
||||||
|
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
|
||||||
|
|
||||||
|
// Set the sampler texture unit to 0
|
||||||
|
GLES20.glUniform1i(samplerHandle, 0);
|
||||||
|
|
||||||
|
GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, 6, GLES20.GL_UNSIGNED_BYTE, indicesBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GbaScreenView(Context context) {
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GbaScreenView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
this.setEGLContextClientVersion(2);
|
||||||
|
this.setPreserveEGLContextOnPause(true);
|
||||||
|
this.setRenderer(this);
|
||||||
|
this.setRenderMode(RENDERMODE_WHEN_DIRTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateFrame(int[] frameBuffer) {
|
||||||
|
this.texture.update(frameBuffer);
|
||||||
|
requestRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
|
||||||
|
texture = new ScreenTexture();
|
||||||
|
|
||||||
|
getHolder().setKeepScreenOn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceChanged(GL10 gl, int width, int height) {
|
||||||
|
gl.glViewport(0, 0, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrawFrame(GL10 gl) {
|
||||||
|
this.texture.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||||
|
holder.setKeepScreenOn(false);
|
||||||
|
this.texture.destroy();
|
||||||
|
super.surfaceDestroyed(holder);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,12 +2,17 @@ package com.mrmichel.rustdroid_emu.ui;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ConfigurationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -43,11 +48,32 @@ public class SplashActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkOpenGLES20() {
|
||||||
|
ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
|
ConfigurationInfo configurationInfo = am.getDeviceConfigurationInfo();
|
||||||
|
if (configurationInfo.reqGlEsVersion >= 0x20000) {
|
||||||
|
// Supported
|
||||||
|
} else {
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setTitle("OpenGLES 2")
|
||||||
|
.setMessage("Your device doesn't support GLES20. reqGLEsVersion = " + configurationInfo.reqGlEsVersion)
|
||||||
|
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
finishAffinity();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.splash_activity);
|
setContentView(R.layout.splash_activity);
|
||||||
|
|
||||||
|
checkOpenGLES20();
|
||||||
|
|
||||||
if (ContextCompat.checkSelfPermission(this,
|
if (ContextCompat.checkSelfPermission(this,
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
!= PackageManager.PERMISSION_GRANTED) {
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
|
|
@ -17,14 +17,12 @@
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
<ImageView
|
<com.mrmichel.rustdroid_emu.ui.GbaScreenView
|
||||||
android:id="@+id/gbaMockImageView"
|
android:id="@+id/gba_view"
|
||||||
android:layout_width="400dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="296dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginStart="20dp"
|
android:layout_marginTop="120dp"
|
||||||
android:layout_marginTop="12dp"
|
app:layout_constraintDimensionRatio="H, 3:2"
|
||||||
android:layout_marginEnd="20dp"
|
|
||||||
android:background="#2D2D33"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.517"
|
app:layout_constraintHorizontal_bias="0.517"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
@ -90,7 +88,7 @@
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="24dp"
|
||||||
android:text="L"
|
android:text="L"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/gbaMockImageView" />
|
app:layout_constraintTop_toBottomOf="@+id/gba_view" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/buttonR"
|
android:id="@+id/buttonR"
|
||||||
|
@ -100,7 +98,7 @@
|
||||||
android:layout_marginEnd="2dp"
|
android:layout_marginEnd="2dp"
|
||||||
android:text="R"
|
android:text="R"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/gbaMockImageView" />
|
app:layout_constraintTop_toBottomOf="@+id/gba_view" />
|
||||||
|
|
||||||
<Switch
|
<Switch
|
||||||
android:id="@+id/tbTurbo"
|
android:id="@+id/tbTurbo"
|
||||||
|
|
Reference in a new issue