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"
|
||||
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.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ import android.view.MenuItem;
|
|||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Switch;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
@ -53,8 +52,8 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL
|
|||
private AudioThread audioThread;
|
||||
private AudioTrack audioTrack;
|
||||
private byte[] on_resume_saved_state = null;
|
||||
private ImageView screen;
|
||||
private boolean turboMode = false;
|
||||
private GbaScreenView gbaScreenView;
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
@ -233,7 +232,6 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL
|
|||
|
||||
|
||||
this.bios = getIntent().getByteArrayExtra("bios");
|
||||
this.screen = findViewById(R.id.gbaMockImageView);
|
||||
this.emulator = new Emulator();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
|
@ -260,6 +258,8 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL
|
|||
AudioTrack.MODE_STREAM);
|
||||
}
|
||||
this.audioTrack.play();
|
||||
|
||||
this.gbaScreenView = findViewById(R.id.gba_view);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -336,10 +336,6 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL
|
|||
startActivityForResult(intent, LOAD_SNAPSHOT_REQUESTCODE);
|
||||
}
|
||||
|
||||
public void updateScreen(Bitmap bmp) {
|
||||
this.screen.setImageBitmap(bmp);
|
||||
}
|
||||
|
||||
private class EmulationRunnable implements Runnable {
|
||||
|
||||
public static final long NANOSECONDS_PER_MILLISECOND = 1000000;
|
||||
|
@ -373,14 +369,7 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL
|
|||
}
|
||||
}
|
||||
|
||||
emulatorActivity.runOnUiThread(new Runnable() {
|
||||
Bitmap bitmap = Bitmap.createBitmap(emulator.getFrameBuffer(), 240, 160, Bitmap.Config.RGB_565);
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
emulatorActivity.updateScreen(bitmap);
|
||||
}
|
||||
});
|
||||
emulatorActivity.gbaScreenView.updateFrame(emulator.getFrameBuffer());
|
||||
}
|
||||
|
||||
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.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ConfigurationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
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
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.splash_activity);
|
||||
|
||||
checkOpenGLES20();
|
||||
|
||||
if (ContextCompat.checkSelfPermission(this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
|
|
|
@ -17,14 +17,12 @@
|
|||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/gbaMockImageView"
|
||||
android:layout_width="400dp"
|
||||
android:layout_height="296dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:background="#2D2D33"
|
||||
<com.mrmichel.rustdroid_emu.ui.GbaScreenView
|
||||
android:id="@+id/gba_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="120dp"
|
||||
app:layout_constraintDimensionRatio="H, 3:2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.517"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
@ -90,7 +88,7 @@
|
|||
android:layout_marginTop="24dp"
|
||||
android:text="L"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/gbaMockImageView" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/gba_view" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonR"
|
||||
|
@ -100,7 +98,7 @@
|
|||
android:layout_marginEnd="2dp"
|
||||
android:text="R"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/gbaMockImageView" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/gba_view" />
|
||||
|
||||
<Switch
|
||||
android:id="@+id/tbTurbo"
|
||||
|
|
Reference in a new issue