diff --git a/AndroidApp/app/build.gradle b/AndroidApp/app/build.gradle index 390b7f1..e1b8c49 100644 --- a/AndroidApp/app/build.gradle +++ b/AndroidApp/app/build.gradle @@ -51,6 +51,7 @@ dependencies { implementation 'com.google.android.material:material:1.1.0' implementation "androidx.documentfile:documentfile:1.0.1" implementation 'androidx.preference:preference:1.1.0' + implementation 'commons-io:commons-io:2.6' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' diff --git a/AndroidApp/app/src/main/AndroidManifest.xml b/AndroidApp/app/src/main/AndroidManifest.xml index 8268750..e528c6e 100644 --- a/AndroidApp/app/src/main/AndroidManifest.xml +++ b/AndroidApp/app/src/main/AndroidManifest.xml @@ -13,10 +13,10 @@ android:allowBackup="true" android:icon="@drawable/icon" android:label="@string/app_name" - android:roundIcon="@mipmap/ic_launcher_round" + android:roundIcon="@drawable/icon" android:supportsRtl="true" android:theme="@style/AppTheme"> - + diff --git a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/EmulatorActivity.java b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/EmulatorActivity.java index 97b6700..7871c7c 100644 --- a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/EmulatorActivity.java +++ b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/EmulatorActivity.java @@ -377,7 +377,7 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this /* Activity context */); - Boolean skipBios = sharedPreferences.getBoolean("skip_bios", false); + boolean skipBios = sharedPreferences.getBoolean("skip_bios", false); if (null != savedInstanceState && (saveFilePath = savedInstanceState.getString("saveFile")) != null) { final EmulatorActivity thisActivity = this; @@ -451,6 +451,10 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL case R.id.action_save_snapshot: doSaveSnapshot(); return true; + case R.id.action_settings: + Intent intent = new Intent(this, SettingsActivity.class); + startActivity(intent); + return true; default: return super.onOptionsItemSelected(item); } diff --git a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/ScreenRenderer.java b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/ScreenRenderer.java index d0a3bce..036f4cd 100644 --- a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/ScreenRenderer.java +++ b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/ScreenRenderer.java @@ -1,13 +1,24 @@ package com.mrmichel.rustdroid_emu.ui; +import android.content.Context; +import android.content.SharedPreferences; import android.graphics.Bitmap; import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.opengl.GLUtils; +import androidx.preference.PreferenceManager; + +import com.mrmichel.rustdroid_emu.R; + +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; +import java.nio.charset.StandardCharsets; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; @@ -16,12 +27,53 @@ public class ScreenRenderer implements GLSurfaceView.Renderer { private ScreenTexture texture; private boolean ready = false; + private Context context; + + public ScreenRenderer(Context context) { + this.context = context; + } + + public void updateTexture(int[] frameBuffer) { + this.texture.update(frameBuffer); + } + + public void initTextureIfNotInitialized() { + if (this.texture == null) { + this.texture = new ScreenTexture(this.context); + } + } + + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + initTextureIfNotInitialized(); + ready = 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(); + } + + public boolean isReady() { + return ready; + } + + public void setColorCorrection(boolean colorCorrection) { + this.texture.setColorCorrection(colorCorrection); + } /** * Private class to manage the screen texture rendering */ private class ScreenTexture { - int shaderProgram; + int normalShaderProgram; + int colorCorrectionShaderProgram; + int currentShaderProgram; int positionHandle; int texCoordHandle; int samplerHandle; @@ -32,6 +84,7 @@ public class ScreenRenderer implements GLSurfaceView.Renderer { private ByteBuffer indicesBuffer; private Bitmap bitmap; + private Context context; // square vertices private float[] vertices = { @@ -55,26 +108,75 @@ public class ScreenRenderer implements GLSurfaceView.Renderer { 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"; + public ScreenTexture(Context context) { + this.context = context; + this.bitmap = Bitmap.createBitmap(240, 160, Bitmap.Config.RGB_565); - 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" + - " vec4 color = texture2D( s_texture, v_texCoord ); \n" + - " gl_FragColor = color; \n" + - "} \n"; + 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(); + + String vertexShader = readShaderResource(R.raw.screen_texture_vertex_shader); + String normalFragmentShader = readShaderResource(R.raw.screen_texture_fragment_shader); + String colorCorrectionFragmentShader = readShaderResource(R.raw.screen_texture_color_correction_fragment_shader); + + normalShaderProgram = createShaderProgram(vertexShader, normalFragmentShader); + colorCorrectionShaderProgram = createShaderProgram(vertexShader, colorCorrectionFragmentShader); + + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this.context); + boolean colorCorrection = sharedPreferences.getBoolean("color_correction", false); + setColorCorrection(colorCorrection); + + GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + } + + private void setColorCorrection(boolean colorCorrection) { + + if (colorCorrection) { + currentShaderProgram = colorCorrectionShaderProgram; + } else { + currentShaderProgram = normalShaderProgram; + } + + // use the program + GLES20.glUseProgram(currentShaderProgram); + + positionHandle = GLES20.glGetAttribLocation(currentShaderProgram, "a_position"); + + texCoordHandle = GLES20.glGetAttribLocation(currentShaderProgram, "a_texCoord"); + + samplerHandle = GLES20.glGetUniformLocation(currentShaderProgram, "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.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); + } private int compileShader(int type, String code) { int shader = GLES20.glCreateShader(type); @@ -123,58 +225,28 @@ public class ScreenRenderer implements GLSurfaceView.Renderer { 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); + private String readShaderResource(int resourceId) { + InputStream in = context.getResources().openRawResource(resourceId); + String code; + try { + code = IOUtils.toString(in, StandardCharsets.UTF_8); + } catch (IOException e) { + code = ""; + } + return code; } - protected void destroy(){ - GLES20.glDeleteProgram(shaderProgram); + protected void destroy() { + GLES20.glDeleteProgram(normalShaderProgram); + GLES20.glDeleteProgram(colorCorrectionShaderProgram); int[] textures = {textureId}; GLES20.glDeleteTextures(1, textures, 0); } public void render() { + // use the shader program + GLES20.glUseProgram(currentShaderProgram); + // clear the color buffer GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); @@ -190,34 +262,4 @@ public class ScreenRenderer implements GLSurfaceView.Renderer { GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, 6, GLES20.GL_UNSIGNED_BYTE, indicesBuffer); } } - - public void updateTexture(int[] frameBuffer) { - this.texture.update(frameBuffer); - } - - public void initTextureIfNotInitialized() { - if (this.texture == null) { - this.texture = new ScreenTexture(); - } - } - - @Override - public void onSurfaceCreated(GL10 gl, EGLConfig config) { - initTextureIfNotInitialized(); - ready = 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(); - } - - public boolean isReady() { - return ready; - } } diff --git a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/ScreenView.java b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/ScreenView.java index a4a9a86..6aa3ff0 100644 --- a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/ScreenView.java +++ b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/ScreenView.java @@ -1,10 +1,13 @@ package com.mrmichel.rustdroid_emu.ui; import android.content.Context; +import android.content.SharedPreferences; import android.opengl.GLSurfaceView; import android.util.AttributeSet; -public class ScreenView extends GLSurfaceView { +import androidx.preference.PreferenceManager; + +public class ScreenView extends GLSurfaceView implements SharedPreferences.OnSharedPreferenceChangeListener { private ScreenRenderer mRenderer; public ScreenView(Context context) { @@ -21,7 +24,11 @@ public class ScreenView extends GLSurfaceView { this.setEGLContextClientVersion(2); this.setPreserveEGLContextOnPause(true); - mRenderer = new ScreenRenderer(); + SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(getContext()); + sharedPreferences.registerOnSharedPreferenceChangeListener(this); + + mRenderer = new ScreenRenderer(getContext()); this.setRenderer(mRenderer); this.setRenderMode(RENDERMODE_WHEN_DIRTY); } @@ -34,4 +41,12 @@ public class ScreenView extends GLSurfaceView { public ScreenRenderer getRenderer() { return mRenderer; } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equals("color_correction")) { + boolean colorCorrection = sharedPreferences.getBoolean("color_correction", false); + mRenderer.setColorCorrection(colorCorrection); + } + } } diff --git a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/SettingsActivity.java b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/SettingsActivity.java index 36df4e2..8223c37 100644 --- a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/SettingsActivity.java +++ b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/SettingsActivity.java @@ -2,18 +2,32 @@ package com.mrmichel.rustdroid_emu.ui; import android.os.Bundle; +import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; +import androidx.preference.PreferenceFragmentCompat; import com.mrmichel.rustdroid_emu.R; public class SettingsActivity extends AppCompatActivity { + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_settings); getSupportFragmentManager() .beginTransaction() - .replace(R.id.settings_container, new SettingsFragment()) + .replace(R.id.settings, new SettingsFragment()) .commit(); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } } -} + + public static class SettingsFragment extends PreferenceFragmentCompat { + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.app_preferences, rootKey); + } + } +} \ No newline at end of file diff --git a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/library/RomListActivity.java b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/library/RomListActivity.java index c78d319..b6edb45 100644 --- a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/library/RomListActivity.java +++ b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/library/RomListActivity.java @@ -13,7 +13,6 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.webkit.MimeTypeMap; import android.widget.AdapterView; import android.widget.GridView; diff --git a/AndroidApp/app/src/main/res/layout/activity_settings.xml b/AndroidApp/app/src/main/res/layout/activity_settings.xml index a9f3452..50e64b2 100644 --- a/AndroidApp/app/src/main/res/layout/activity_settings.xml +++ b/AndroidApp/app/src/main/res/layout/activity_settings.xml @@ -1,13 +1,9 @@ - + android:layout_height="match_parent"> - - + android:layout_height="match_parent" /> \ No newline at end of file diff --git a/AndroidApp/app/src/main/res/raw/screen_texture_color_correction_fragment_shader.glsl b/AndroidApp/app/src/main/res/raw/screen_texture_color_correction_fragment_shader.glsl new file mode 100644 index 0000000..2692b64 --- /dev/null +++ b/AndroidApp/app/src/main/res/raw/screen_texture_color_correction_fragment_shader.glsl @@ -0,0 +1,21 @@ +/* + Port of byuu's color correction shader as described in https://byuu.net/video/color-emulation + */ + +precision mediump float; + +varying vec2 v_texCoord; +uniform sampler2D s_texture; + +void main() +{ + float lcdGamma = 4.0, outGamma = 2.2; + + vec4 color = texture2D( s_texture, v_texCoord ); + + color.rgb = pow(color.rgb, vec3(lcdGamma)); + gl_FragColor.r = pow(( 0.0 * color.b + 50.0 * color.g + 255.0 * color.r) / 255.0, 1.0 / outGamma) * 255.0 / 280.0; + gl_FragColor.g = pow(( 30.0 * color.b + 230.0 * color.g + 10.0 * color.r) / 255.0, 1.0 / outGamma) * 255.0 / 280.0; + gl_FragColor.b = pow((220.0 * color.b + 10.0 * color.g + 50.0 * color.r) / 255.0, 1.0 / outGamma) * 255.0 / 280.0; + gl_FragColor.a = 1.0; +} \ No newline at end of file diff --git a/AndroidApp/app/src/main/res/raw/screen_texture_fragment_shader.glsl b/AndroidApp/app/src/main/res/raw/screen_texture_fragment_shader.glsl new file mode 100644 index 0000000..e77b894 --- /dev/null +++ b/AndroidApp/app/src/main/res/raw/screen_texture_fragment_shader.glsl @@ -0,0 +1,8 @@ +precision mediump float; +varying vec2 v_texCoord; +uniform sampler2D s_texture; +void main() +{ + vec4 color = texture2D( s_texture, v_texCoord ); + gl_FragColor = color; +} \ No newline at end of file diff --git a/AndroidApp/app/src/main/res/raw/screen_texture_vertex_shader.glsl b/AndroidApp/app/src/main/res/raw/screen_texture_vertex_shader.glsl new file mode 100644 index 0000000..77742ce --- /dev/null +++ b/AndroidApp/app/src/main/res/raw/screen_texture_vertex_shader.glsl @@ -0,0 +1,8 @@ +attribute vec4 a_position; +attribute vec2 a_texCoord; +varying vec2 v_texCoord; +void main() +{ + gl_Position = a_position; + v_texCoord = a_texCoord; +} \ No newline at end of file diff --git a/AndroidApp/app/src/main/res/values/strings.xml b/AndroidApp/app/src/main/res/values/strings.xml index 87b094b..8687a6b 100644 --- a/AndroidApp/app/src/main/res/values/strings.xml +++ b/AndroidApp/app/src/main/res/values/strings.xml @@ -16,4 +16,9 @@ Delete Snapshot Manager + + Apply color correction shader + + Makes colors look closer to the real GBA LCD screen. + diff --git a/AndroidApp/app/src/main/res/xml/app_preferences.xml b/AndroidApp/app/src/main/res/xml/app_preferences.xml index 26b0f1f..955e9d8 100644 --- a/AndroidApp/app/src/main/res/xml/app_preferences.xml +++ b/AndroidApp/app/src/main/res/xml/app_preferences.xml @@ -1,8 +1,18 @@ - + + + + + + + \ No newline at end of file