mirror of
https://github.com/rafaelvcaetano/melonDS-android.git
synced 2024-11-26 23:20:40 +00:00
Merge branch 'opengl_renderer'
This commit is contained in:
commit
52f9022fad
@ -32,6 +32,7 @@ if (ENABLE_JIT)
|
||||
add_definitions(-DJIT_ENABLED)
|
||||
endif()
|
||||
|
||||
add_definitions(-DENABLE_OGLRENDERER)
|
||||
set(CORE-LIB ../melonDS-android-lib)
|
||||
add_subdirectory(${CORE-LIB} ./melonDS-android-lib)
|
||||
include_directories(${CORE-LIB}/src/android)
|
||||
|
@ -10,6 +10,8 @@
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
|
||||
<uses-feature android:glEsVersion="0x00030002" android:required="false" />
|
||||
|
||||
<application
|
||||
android:name=".MelonDSApplication"
|
||||
android:allowBackup="true"
|
||||
|
@ -10,9 +10,13 @@ MelonDSAndroid::EmulatorConfiguration MelonDSAndroidConfiguration::buildEmulator
|
||||
jclass audioInterpolationEnumClass = env->FindClass("me/magnum/melonds/domain/model/AudioInterpolation");
|
||||
jclass audioLatencyEnumClass = env->FindClass("me/magnum/melonds/domain/model/AudioLatency");
|
||||
jclass micSourceEnumClass = env->FindClass("me/magnum/melonds/domain/model/MicSource");
|
||||
jclass videoRendererEnumClass = env->FindClass("me/magnum/melonds/domain/model/VideoRenderer");
|
||||
jclass renderConfigurationClass = env->FindClass("me/magnum/melonds/domain/model/RendererConfiguration");
|
||||
|
||||
jmethodID uriToStringMethod = env->GetMethodID(uriClass, "toString", "()Ljava/lang/String;");
|
||||
|
||||
jobject firmwareConfigurationObject = env->GetObjectField(emulatorConfiguration, env->GetFieldID(emulatorConfigurationClass, "firmwareConfiguration", "Lme/magnum/melonds/domain/model/FirmwareConfiguration;"));
|
||||
jobject rendererConfigurationObject = env->GetObjectField(emulatorConfiguration, env->GetFieldID(emulatorConfigurationClass, "rendererConfiguration", "Lme/magnum/melonds/domain/model/RendererConfiguration;"));
|
||||
jboolean useCustomBios = env->GetBooleanField(emulatorConfiguration, env->GetFieldID(emulatorConfigurationClass, "useCustomBios", "Z"));
|
||||
jboolean showBootScreen = env->GetBooleanField(emulatorConfiguration, env->GetFieldID(emulatorConfigurationClass, "showBootScreen", "Z"));
|
||||
jobject dsBios7Uri = env->GetObjectField(emulatorConfiguration, env->GetFieldID(emulatorConfigurationClass, "dsBios7Uri", "Landroid/net/Uri;"));
|
||||
@ -40,6 +44,8 @@ MelonDSAndroid::EmulatorConfiguration MelonDSAndroidConfiguration::buildEmulator
|
||||
jint audioLatency = env->GetIntField(audioLatencyEnum, env->GetFieldID(audioLatencyEnumClass, "latencyValue", "I"));
|
||||
jobject micSourceEnum = env->GetObjectField(emulatorConfiguration, env->GetFieldID(emulatorConfigurationClass, "micSource", "Lme/magnum/melonds/domain/model/MicSource;"));
|
||||
jint micSource = env->GetIntField(micSourceEnum, env->GetFieldID(micSourceEnumClass, "sourceValue", "I"));
|
||||
jobject videoRendererEnum = env->GetObjectField(rendererConfigurationObject, env->GetFieldID(renderConfigurationClass, "renderer", "Lme/magnum/melonds/domain/model/VideoRenderer;"));
|
||||
jint videoRenderer = env->GetIntField(videoRendererEnum, env->GetFieldID(videoRendererEnumClass, "renderer", "I"));
|
||||
jboolean isCopy = JNI_FALSE;
|
||||
jstring dsBios7String = dsBios7Uri ? (jstring) env->CallObjectMethod(dsBios7Uri, uriToStringMethod) : nullptr;
|
||||
jstring dsBios9String = dsBios9Uri ? (jstring) env->CallObjectMethod(dsBios9Uri, uriToStringMethod) : nullptr;
|
||||
@ -56,8 +62,6 @@ MelonDSAndroid::EmulatorConfiguration MelonDSAndroidConfiguration::buildEmulator
|
||||
const char* dsiFirmwarePath = dsiFirmwareUri ? env->GetStringUTFChars(dsiFirmwareString, &isCopy) : nullptr;
|
||||
const char* dsiNandPath = dsiNandUri ? env->GetStringUTFChars(dsiNandString, &isCopy) : nullptr;
|
||||
const char* internalDir = env->GetStringUTFChars(internalFilesDir, nullptr);
|
||||
jobject firmwareConfigurationObject = env->GetObjectField(emulatorConfiguration, env->GetFieldID(emulatorConfigurationClass, "firmwareConfiguration", "Lme/magnum/melonds/domain/model/FirmwareConfiguration;"));
|
||||
jobject rendererConfigurationObject = env->GetObjectField(emulatorConfiguration, env->GetFieldID(emulatorConfigurationClass, "rendererConfiguration", "Lme/magnum/melonds/domain/model/RendererConfiguration;"));
|
||||
|
||||
MelonDSAndroid::EmulatorConfiguration finalEmulatorConfiguration;
|
||||
finalEmulatorConfiguration.userInternalFirmwareAndBios = !useCustomBios;
|
||||
@ -84,6 +88,7 @@ MelonDSAndroid::EmulatorConfiguration MelonDSAndroidConfiguration::buildEmulator
|
||||
finalEmulatorConfiguration.rewindEnabled = enableRewind ? 1 : 0;
|
||||
finalEmulatorConfiguration.rewindCaptureSpacingSeconds = rewindPeriodSeconds;
|
||||
finalEmulatorConfiguration.rewindLengthSeconds = rewindWindowSeconds;
|
||||
finalEmulatorConfiguration.renderer = videoRenderer;
|
||||
return finalEmulatorConfiguration;
|
||||
}
|
||||
|
||||
@ -134,10 +139,12 @@ MelonDSAndroid::FirmwareConfiguration MelonDSAndroidConfiguration::buildFirmware
|
||||
|
||||
GPU::RenderSettings MelonDSAndroidConfiguration::buildRenderSettings(JNIEnv* env, jobject renderSettings) {
|
||||
jclass renderSettingsClass = env->GetObjectClass(renderSettings);
|
||||
jmethodID getResolutionScalingMethod = env->GetMethodID(renderSettingsClass, "getResolutionScaling", "()I");
|
||||
jboolean threadedRendering = env->GetBooleanField(renderSettings, env->GetFieldID(renderSettingsClass, "threadedRendering", "Z"));
|
||||
jint internalResolutionScaling = env->CallIntMethod(renderSettings, getResolutionScalingMethod);
|
||||
return {
|
||||
threadedRendering == JNI_TRUE,
|
||||
1,
|
||||
false
|
||||
threadedRendering == JNI_TRUE,
|
||||
internalResolutionScaling,
|
||||
false
|
||||
};
|
||||
}
|
@ -452,10 +452,12 @@ Java_me_magnum_melonds_MelonEmulator_setFastForwardEnabled(JNIEnv* env, jobject
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_me_magnum_melonds_MelonEmulator_updateEmulatorConfiguration(JNIEnv* env, jobject thiz, jobject emulatorConfiguration)
|
||||
Java_me_magnum_melonds_MelonEmulator_updateEmulatorConfiguration(JNIEnv* env, jobject thiz, jobject emulatorConfiguration, jobject frameBuffer)
|
||||
{
|
||||
MelonDSAndroid::EmulatorConfiguration newConfiguration = MelonDSAndroidConfiguration::buildEmulatorConfiguration(env, emulatorConfiguration);
|
||||
MelonDSAndroid::updateEmulatorConfiguration(newConfiguration);
|
||||
u32* frameBufferPointer = (u32*) env->GetDirectBufferAddress(frameBuffer);
|
||||
|
||||
MelonDSAndroid::updateEmulatorConfiguration(newConfiguration, frameBufferPointer);
|
||||
fastForwardSpeedMultiplier = newConfiguration.fastForwardSpeedMultiplier;
|
||||
|
||||
if (isFastForwardEnabled) {
|
||||
|
@ -113,5 +113,5 @@ object MelonEmulator {
|
||||
|
||||
external fun setFastForwardEnabled(enabled: Boolean)
|
||||
|
||||
external fun updateEmulatorConfiguration(emulatorConfiguration: EmulatorConfiguration)
|
||||
external fun updateEmulatorConfiguration(emulatorConfiguration: EmulatorConfiguration, frameBuffer: ByteBuffer)
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package me.magnum.melonds.common.runtime
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import me.magnum.melonds.domain.model.RendererConfiguration
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
@ -11,23 +12,41 @@ class FrameBufferProvider {
|
||||
private const val SCREEN_HEIGHT = 384
|
||||
}
|
||||
|
||||
private val frameBuffer by lazy {
|
||||
ByteBuffer.allocateDirect(SCREEN_WIDTH * SCREEN_HEIGHT * 4).order(ByteOrder.nativeOrder())
|
||||
private var rendererConfiguration: RendererConfiguration? = null
|
||||
|
||||
private var frameBuffer: ByteBuffer? = null
|
||||
|
||||
fun setRendererConfiguration(configuration: RendererConfiguration) {
|
||||
val mustResizeFrameBuffer = isFrameBufferReady() && rendererConfiguration?.resolutionScaling != configuration.resolutionScaling
|
||||
rendererConfiguration = configuration
|
||||
|
||||
if (mustResizeFrameBuffer) {
|
||||
frameBuffer = null
|
||||
ensureFrameBufferIsReady()
|
||||
}
|
||||
}
|
||||
|
||||
fun isFrameBufferReady(): Boolean {
|
||||
return frameBuffer != null
|
||||
}
|
||||
|
||||
fun frameBuffer(): ByteBuffer {
|
||||
return frameBuffer
|
||||
return ensureFrameBufferIsReady()
|
||||
}
|
||||
|
||||
fun getScreenshot(): Bitmap {
|
||||
val frameBuffer = ensureFrameBufferIsReady()
|
||||
|
||||
val scale = rendererConfiguration?.resolutionScaling ?: 1
|
||||
val scaledWidth = SCREEN_WIDTH * scale
|
||||
|
||||
return Bitmap.createBitmap(SCREEN_WIDTH, SCREEN_HEIGHT, Bitmap.Config.ARGB_8888).apply {
|
||||
// Texture buffer is in BGR format. Convert to RGB
|
||||
for (x in 0 until SCREEN_WIDTH) {
|
||||
for (y in 0 until SCREEN_HEIGHT) {
|
||||
val b = frameBuffer[(y * SCREEN_WIDTH + x) * 4 + 0].toInt() and 0xFF
|
||||
val g = frameBuffer[(y * SCREEN_WIDTH + x) * 4 + 1].toInt() and 0xFF
|
||||
val r = frameBuffer[(y * SCREEN_WIDTH + x) * 4 + 2].toInt() and 0xFF
|
||||
val argbPixel = 0xFF000000.toInt() or r.shl(16) or g.shl(8) or b
|
||||
val pixelPosition = (y * scale * scaledWidth + x * scale) * 4
|
||||
// There's no need to do a manual pixel format conversion. Since getInt() uses the buffer's byte order, which is little endian, it will automatically
|
||||
// convert the internal BGRA format into the ARGB format, which is what we need to build the bitmap
|
||||
val argbPixel = frameBuffer.getInt(pixelPosition)
|
||||
setPixel(x, y, argbPixel)
|
||||
}
|
||||
}
|
||||
@ -35,9 +54,23 @@ class FrameBufferProvider {
|
||||
}
|
||||
|
||||
fun clearFrameBuffer() {
|
||||
frameBuffer.position(0)
|
||||
repeat(frameBuffer.capacity() / 4) {
|
||||
frameBuffer.putInt(0xFF000000.toInt())
|
||||
frameBuffer?.let { buffer ->
|
||||
buffer.position(0)
|
||||
repeat(buffer.capacity() / 4) {
|
||||
buffer.putInt(0xFF000000.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ensureFrameBufferIsReady(): ByteBuffer {
|
||||
if (frameBuffer != null) {
|
||||
return frameBuffer!!
|
||||
}
|
||||
|
||||
val rendererConfiguration = requireNotNull(rendererConfiguration)
|
||||
val scaledWidth = SCREEN_WIDTH * rendererConfiguration.resolutionScaling
|
||||
val scaledHeight= SCREEN_HEIGHT * rendererConfiguration.resolutionScaling
|
||||
frameBuffer = ByteBuffer.allocateDirect(scaledWidth * scaledHeight * 4).order(ByteOrder.nativeOrder())
|
||||
return frameBuffer!!
|
||||
}
|
||||
}
|
@ -12,6 +12,8 @@ import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import me.magnum.melonds.common.romprocessors.RomFileProcessorFactory
|
||||
import me.magnum.melonds.common.uridelegates.UriHandler
|
||||
import me.magnum.melonds.common.vibration.Api26VibratorDelegate
|
||||
@ -37,7 +39,7 @@ object MelonModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSettingsRepository(@ApplicationContext context: Context, sharedPreferences: SharedPreferences, gson: Gson, uriHandler: UriHandler): SettingsRepository {
|
||||
return SharedPreferencesSettingsRepository(context, sharedPreferences, gson, uriHandler)
|
||||
return SharedPreferencesSettingsRepository(context, sharedPreferences, gson, uriHandler, CoroutineScope(Dispatchers.IO))
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
@ -1,3 +1,19 @@
|
||||
package me.magnum.melonds.domain.model
|
||||
|
||||
data class RendererConfiguration(val videoFiltering: VideoFiltering, val threadedRendering: Boolean)
|
||||
data class RendererConfiguration(
|
||||
val renderer: VideoRenderer,
|
||||
private val internalVideoFiltering: VideoFiltering,
|
||||
val threadedRendering: Boolean,
|
||||
private val internalResolutionScaling: Int,
|
||||
) {
|
||||
|
||||
val videoFiltering get() = when (renderer) {
|
||||
VideoRenderer.SOFTWARE -> internalVideoFiltering
|
||||
VideoRenderer.OPENGL -> VideoFiltering.NONE
|
||||
}
|
||||
|
||||
val resolutionScaling get() = when (renderer) {
|
||||
VideoRenderer.SOFTWARE -> 1
|
||||
VideoRenderer.OPENGL -> internalResolutionScaling
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package me.magnum.melonds.domain.model
|
||||
|
||||
enum class VideoRenderer(val renderer: Int) {
|
||||
SOFTWARE(0),
|
||||
OPENGL(1),
|
||||
}
|
@ -29,8 +29,10 @@ interface SettingsRepository {
|
||||
fun showBootScreen(): Boolean
|
||||
fun isJitEnabled(): Boolean
|
||||
|
||||
fun getVideoRenderer(): Flow<VideoRenderer>
|
||||
fun getVideoInternalResolutionScaling(): Flow<Int>
|
||||
fun getVideoFiltering(): Flow<VideoFiltering>
|
||||
fun isThreadedRenderingEnabled(): Boolean
|
||||
fun isThreadedRenderingEnabled(): Flow<Boolean>
|
||||
fun getFpsCounterPosition(): FpsCounterPosition
|
||||
fun getDSiCameraSource(): DSiCameraSourceType
|
||||
fun getDSiCameraStaticImage(): Uri?
|
||||
@ -73,4 +75,6 @@ interface SettingsRepository {
|
||||
fun setRomSortingMode(sortingMode: SortingMode)
|
||||
fun setRomSortingOrder(sortingOrder: SortingOrder)
|
||||
fun setSelectedLayoutId(layoutId: UUID)
|
||||
|
||||
fun observeRenderConfiguration(): Flow<RendererConfiguration>
|
||||
}
|
||||
|
@ -12,13 +12,38 @@ import androidx.documentfile.provider.DocumentFile
|
||||
import com.google.gson.Gson
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.subjects.PublishSubject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import me.magnum.melonds.common.uridelegates.UriHandler
|
||||
import me.magnum.melonds.domain.model.*
|
||||
import me.magnum.melonds.domain.model.AudioBitrate
|
||||
import me.magnum.melonds.domain.model.AudioInterpolation
|
||||
import me.magnum.melonds.domain.model.AudioLatency
|
||||
import me.magnum.melonds.domain.model.ConsoleType
|
||||
import me.magnum.melonds.domain.model.ControllerConfiguration
|
||||
import me.magnum.melonds.domain.model.EmulatorConfiguration
|
||||
import me.magnum.melonds.domain.model.FirmwareConfiguration
|
||||
import me.magnum.melonds.domain.model.FpsCounterPosition
|
||||
import me.magnum.melonds.domain.model.LayoutConfiguration
|
||||
import me.magnum.melonds.domain.model.MacAddress
|
||||
import me.magnum.melonds.domain.model.MicSource
|
||||
import me.magnum.melonds.domain.model.RendererConfiguration
|
||||
import me.magnum.melonds.domain.model.Rom
|
||||
import me.magnum.melonds.domain.model.RomIconFiltering
|
||||
import me.magnum.melonds.domain.model.SaveStateLocation
|
||||
import me.magnum.melonds.domain.model.SizeUnit
|
||||
import me.magnum.melonds.domain.model.SortingMode
|
||||
import me.magnum.melonds.domain.model.SortingOrder
|
||||
import me.magnum.melonds.domain.model.VideoFiltering
|
||||
import me.magnum.melonds.domain.model.VideoRenderer
|
||||
import me.magnum.melonds.domain.model.camera.DSiCameraSourceType
|
||||
import me.magnum.melonds.domain.repositories.SettingsRepository
|
||||
import me.magnum.melonds.extensions.isSustainedPerformanceModeAvailable
|
||||
@ -28,14 +53,15 @@ import java.io.File
|
||||
import java.io.FileReader
|
||||
import java.io.IOException
|
||||
import java.io.OutputStreamWriter
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
import kotlin.math.pow
|
||||
|
||||
class SharedPreferencesSettingsRepository(
|
||||
private val context: Context,
|
||||
private val preferences: SharedPreferences,
|
||||
private val gson: Gson,
|
||||
private val uriHandler: UriHandler
|
||||
private val uriHandler: UriHandler,
|
||||
preferencesCoroutineScope: CoroutineScope,
|
||||
) : SettingsRepository, OnSharedPreferenceChangeListener {
|
||||
|
||||
companion object {
|
||||
@ -46,11 +72,21 @@ class SharedPreferencesSettingsRepository(
|
||||
private var controllerConfiguration: ControllerConfiguration? = null
|
||||
private val preferenceObservers: HashMap<String, PublishSubject<Any>> = HashMap()
|
||||
private val preferenceSharedFlows = mutableMapOf<String, MutableSharedFlow<Unit>>()
|
||||
private val renderConfigurationFlow: SharedFlow<RendererConfiguration>
|
||||
|
||||
init {
|
||||
preferences.registerOnSharedPreferenceChangeListener(this)
|
||||
setDefaultThemeIfRequired()
|
||||
setDefaultMacAddressIfRequired()
|
||||
|
||||
renderConfigurationFlow = combine(
|
||||
getVideoRenderer(),
|
||||
getVideoFiltering(),
|
||||
isThreadedRenderingEnabled(),
|
||||
getVideoInternalResolutionScaling(),
|
||||
) { renderer, filtering, threadedRenderingEnabled, resolutionScaling ->
|
||||
RendererConfiguration(renderer, filtering, threadedRenderingEnabled, resolutionScaling)
|
||||
}.conflate().shareIn(preferencesCoroutineScope, SharingStarted.Lazily, replay = 1)
|
||||
}
|
||||
|
||||
private fun setDefaultThemeIfRequired() {
|
||||
@ -115,10 +151,7 @@ class SharedPreferencesSettingsRepository(
|
||||
getAudioLatency(),
|
||||
getMicSource(),
|
||||
getFirmwareConfiguration(),
|
||||
RendererConfiguration(
|
||||
getVideoFiltering().first(),
|
||||
isThreadedRenderingEnabled()
|
||||
)
|
||||
renderConfigurationFlow.first(),
|
||||
)
|
||||
}
|
||||
|
||||
@ -232,6 +265,20 @@ class SharedPreferencesSettingsRepository(
|
||||
return preferences.getBoolean("enable_jit", defaultJitEnabled)
|
||||
}
|
||||
|
||||
override fun getVideoRenderer(): Flow<VideoRenderer> {
|
||||
return getOrCreatePreferenceSharedFlow("video_renderer") {
|
||||
val videoRendererPreference = preferences.getString("video_renderer", "software")!!
|
||||
VideoRenderer.valueOf(videoRendererPreference.uppercase())
|
||||
}
|
||||
}
|
||||
|
||||
override fun getVideoInternalResolutionScaling(): Flow<Int> {
|
||||
return getOrCreatePreferenceSharedFlow("video_internal_resolution") {
|
||||
val internalResolutionPreference = preferences.getString("video_internal_resolution", "1")!!
|
||||
internalResolutionPreference.toIntOrNull() ?: 1
|
||||
}
|
||||
}
|
||||
|
||||
override fun getVideoFiltering(): Flow<VideoFiltering> {
|
||||
return getOrCreatePreferenceSharedFlow("video_filtering") {
|
||||
val filteringPreference = preferences.getString("video_filtering", "linear")!!
|
||||
@ -239,8 +286,10 @@ class SharedPreferencesSettingsRepository(
|
||||
}
|
||||
}
|
||||
|
||||
override fun isThreadedRenderingEnabled(): Boolean {
|
||||
return preferences.getBoolean("enable_threaded_rendering", true)
|
||||
override fun isThreadedRenderingEnabled(): Flow<Boolean> {
|
||||
return getOrCreatePreferenceSharedFlow("enable_threaded_rendering") {
|
||||
preferences.getBoolean("enable_threaded_rendering", true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFpsCounterPosition(): FpsCounterPosition {
|
||||
@ -518,4 +567,8 @@ class SharedPreferencesSettingsRepository(
|
||||
|
||||
preferenceSharedFlows[key]?.tryEmit(Unit)
|
||||
}
|
||||
|
||||
override fun observeRenderConfiguration(): Flow<RendererConfiguration> {
|
||||
return renderConfigurationFlow
|
||||
}
|
||||
}
|
@ -89,12 +89,14 @@ class AndroidEmulatorManager(
|
||||
|
||||
override suspend fun updateRomEmulatorConfiguration(rom: Rom) {
|
||||
val configuration = getRomEmulatorConfiguration(rom)
|
||||
MelonEmulator.updateEmulatorConfiguration(configuration)
|
||||
frameBufferProvider.setRendererConfiguration(configuration.rendererConfiguration)
|
||||
MelonEmulator.updateEmulatorConfiguration(configuration, frameBufferProvider.frameBuffer())
|
||||
}
|
||||
|
||||
override suspend fun updateFirmwareEmulatorConfiguration(consoleType: ConsoleType) {
|
||||
val configuration = getFirmwareEmulatorConfiguration(consoleType)
|
||||
MelonEmulator.updateEmulatorConfiguration(configuration)
|
||||
frameBufferProvider.setRendererConfiguration(configuration.rendererConfiguration)
|
||||
MelonEmulator.updateEmulatorConfiguration(configuration, frameBufferProvider.frameBuffer())
|
||||
}
|
||||
|
||||
override suspend fun getRewindWindow(): RewindWindow {
|
||||
@ -166,6 +168,8 @@ class AndroidEmulatorManager(
|
||||
}
|
||||
|
||||
private fun setupEmulator(emulatorConfiguration: EmulatorConfiguration) {
|
||||
frameBufferProvider.setRendererConfiguration(emulatorConfiguration.rendererConfiguration)
|
||||
|
||||
MelonEmulator.setupEmulator(
|
||||
emulatorConfiguration,
|
||||
context.assets,
|
||||
|
@ -9,6 +9,7 @@ import android.opengl.Matrix
|
||||
import me.magnum.melonds.common.opengl.Shader
|
||||
import me.magnum.melonds.common.opengl.ShaderFactory
|
||||
import me.magnum.melonds.common.opengl.ShaderProgramSource
|
||||
import me.magnum.melonds.common.runtime.FrameBufferProvider
|
||||
import me.magnum.melonds.domain.model.BackgroundMode
|
||||
import me.magnum.melonds.domain.model.Rect
|
||||
import me.magnum.melonds.domain.model.RuntimeBackground
|
||||
@ -23,7 +24,7 @@ import javax.microedition.khronos.opengles.GL10
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class DSRenderer(
|
||||
private val frameBuffer: ByteBuffer,
|
||||
private val frameBufferProvider: FrameBufferProvider,
|
||||
private val context: Context,
|
||||
) : GLSurfaceView.Renderer {
|
||||
companion object {
|
||||
@ -70,6 +71,9 @@ class DSRenderer(
|
||||
private var width = 0f
|
||||
private var height = 0f
|
||||
|
||||
private var internalWidth = 0
|
||||
private var internalHeight = 0
|
||||
|
||||
private var backgroundWidth = 0
|
||||
private var backgroundHeight = 0
|
||||
|
||||
@ -136,6 +140,9 @@ class DSRenderer(
|
||||
}
|
||||
|
||||
private fun applyRendererConfiguration() {
|
||||
internalWidth = SCREEN_WIDTH * (rendererConfiguration?.resolutionScaling ?: 1)
|
||||
internalHeight = SCREEN_HEIGHT * (rendererConfiguration?.resolutionScaling ?: 1)
|
||||
|
||||
updateScreenCoordinates()
|
||||
updateShader()
|
||||
}
|
||||
@ -275,6 +282,12 @@ class DSRenderer(
|
||||
mustUpdateConfiguration = false
|
||||
}
|
||||
|
||||
if (!frameBufferProvider.isFrameBufferReady()) {
|
||||
return
|
||||
}
|
||||
|
||||
val frameBuffer = frameBufferProvider.frameBuffer()
|
||||
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
|
||||
synchronized(backgroundLock) {
|
||||
@ -290,7 +303,7 @@ class DSRenderer(
|
||||
it.use()
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mainTexture)
|
||||
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, SCREEN_WIDTH, SCREEN_HEIGHT, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, frameBuffer)
|
||||
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, internalWidth, internalHeight, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, frameBuffer)
|
||||
GLES20.glUniformMatrix4fv(it.uniformMvp, 1, false, mvpMatrix, 0)
|
||||
GLES20.glVertexAttribPointer(it.attribPos, 2, GLES20.GL_FLOAT, false, 0, posBuffer)
|
||||
GLES20.glVertexAttribPointer(it.attribUv, 2, GLES20.GL_FLOAT, false, 0, uvBuffer)
|
||||
|
@ -234,7 +234,7 @@ class EmulatorActivity : AppCompatActivity() {
|
||||
onBackPressedDispatcher.addCallback(backPressedCallback)
|
||||
|
||||
melonTouchHandler = MelonTouchHandler()
|
||||
dsRenderer = DSRenderer(frameBufferProvider.frameBuffer(), this)
|
||||
dsRenderer = DSRenderer(frameBufferProvider, this)
|
||||
binding.surfaceMain.apply {
|
||||
setEGLContextClientVersion(2)
|
||||
preserveEGLContextOnPause = true
|
||||
|
@ -562,8 +562,8 @@ class EmulatorViewModel @Inject constructor(
|
||||
|
||||
private fun startObservingRendererConfiguration() {
|
||||
sessionCoroutineScope.launch {
|
||||
settingsRepository.getVideoFiltering().collectLatest {
|
||||
_runtimeRendererConfiguration.value = RuntimeRendererConfiguration(it)
|
||||
settingsRepository.observeRenderConfiguration().collectLatest {
|
||||
_runtimeRendererConfiguration.value = RuntimeRendererConfiguration(it.videoFiltering, it.resolutionScaling)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,4 +4,5 @@ import me.magnum.melonds.domain.model.VideoFiltering
|
||||
|
||||
data class RuntimeRendererConfiguration(
|
||||
val videoFiltering: VideoFiltering,
|
||||
val resolutionScaling: Int,
|
||||
)
|
@ -2,11 +2,13 @@ package me.magnum.melonds.ui.settings.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import me.magnum.melonds.R
|
||||
import me.magnum.melonds.common.DirectoryAccessValidator
|
||||
import me.magnum.melonds.common.UriPermissionManager
|
||||
import me.magnum.melonds.domain.model.VideoRenderer
|
||||
import me.magnum.melonds.domain.model.camera.DSiCameraSourceType
|
||||
import me.magnum.melonds.ui.settings.PreferenceFragmentHelper
|
||||
import me.magnum.melonds.ui.settings.PreferenceFragmentTitleProvider
|
||||
@ -21,12 +23,29 @@ class VideoPreferencesFragment : PreferenceFragmentCompat(), PreferenceFragmentT
|
||||
@Inject lateinit var uriPermissionManager: UriPermissionManager
|
||||
@Inject lateinit var directoryAccessValidator: DirectoryAccessValidator
|
||||
|
||||
private val softwareRendererPreferences = mutableListOf<Preference>()
|
||||
private val openGlRendererPreferences = mutableListOf<Preference>()
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.pref_video, rootKey)
|
||||
|
||||
softwareRendererPreferences.apply {
|
||||
add(findPreference("video_filtering")!!)
|
||||
add(findPreference("enable_threaded_rendering")!!)
|
||||
}
|
||||
|
||||
openGlRendererPreferences.apply {
|
||||
add(findPreference("video_internal_resolution")!!)
|
||||
}
|
||||
|
||||
val rendererPreference = findPreference<ListPreference>("video_renderer")!!
|
||||
val dsiCameraSourcePreference = findPreference<ListPreference>("dsi_camera_source")!!
|
||||
val dsiCameraImagePreference = findPreference<StoragePickerPreference>("dsi_camera_static_image")!!
|
||||
|
||||
rendererPreference.setOnPreferenceChangeListener { _, newValue ->
|
||||
onRendererPreferenceChanged(newValue as String)
|
||||
true
|
||||
}
|
||||
dsiCameraSourcePreference.setOnPreferenceChangeListener { _, newValue ->
|
||||
updateDsiCameraImagePreference(dsiCameraImagePreference, newValue as String)
|
||||
true
|
||||
@ -34,9 +53,32 @@ class VideoPreferencesFragment : PreferenceFragmentCompat(), PreferenceFragmentT
|
||||
|
||||
helper.setupStoragePickerPreference(dsiCameraImagePreference)
|
||||
|
||||
onRendererPreferenceChanged(rendererPreference.value)
|
||||
updateDsiCameraImagePreference(dsiCameraImagePreference, dsiCameraSourcePreference.value)
|
||||
}
|
||||
|
||||
private fun onRendererPreferenceChanged(rendererValue: String) {
|
||||
val newRenderer = enumValueOfIgnoreCase<VideoRenderer>(rendererValue)
|
||||
when (newRenderer) {
|
||||
VideoRenderer.SOFTWARE -> {
|
||||
softwareRendererPreferences.forEach {
|
||||
it.isVisible = true
|
||||
}
|
||||
openGlRendererPreferences.forEach {
|
||||
it.isVisible = false
|
||||
}
|
||||
}
|
||||
VideoRenderer.OPENGL -> {
|
||||
softwareRendererPreferences.forEach {
|
||||
it.isVisible = false
|
||||
}
|
||||
openGlRendererPreferences.forEach {
|
||||
it.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateDsiCameraImagePreference(preference: StoragePickerPreference, dsiCameraSourceValue: String) {
|
||||
val newSource = enumValueOfIgnoreCase<DSiCameraSourceType>(dsiCameraSourceValue)
|
||||
preference.isEnabled = newSource == DSiCameraSourceType.STATIC_IMAGE
|
||||
|
@ -29,6 +29,22 @@
|
||||
<item>bottom_right</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="video_renderer_values">
|
||||
<item>software</item>
|
||||
<item>opengl</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="video_internal_resolution_values">
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
<item>6</item>
|
||||
<item>7</item>
|
||||
<item>8</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="video_filtering_values">
|
||||
<item>none</item>
|
||||
<item>linear</item>
|
||||
|
@ -183,6 +183,8 @@
|
||||
<string name="category_video">Video</string>
|
||||
<string name="category_video_summary">Filter, threaded rendering, FPS counter</string>
|
||||
<string name="filter">Filter</string>
|
||||
<string name="internal_resolution">Internal resolution</string>
|
||||
<string name="renderer">Renderer</string>
|
||||
<string name="dsi_camera_source">DSi camera source</string>
|
||||
<string name="dsi_camera_image">DSi camera image</string>
|
||||
<string name="category_audio">Audio</string>
|
||||
@ -397,6 +399,22 @@
|
||||
<item>Bottom right</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="video_renderer_options">
|
||||
<item>Software</item>
|
||||
<item>OpenGL</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="video_internal_resolution_options">
|
||||
<item>1x native (256x192)</item>
|
||||
<item>2x native (512x384)</item>
|
||||
<item>3x native (768x576)</item>
|
||||
<item>4x native (1024x768)</item>
|
||||
<item>5x native (1280x960)</item>
|
||||
<item>6x native (1536x1152)</item>
|
||||
<item>7x native (1792x1344)</item>
|
||||
<item>8x native (2048x1536)</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="video_filtering_options">
|
||||
<item>None</item>
|
||||
<item>Linear</item>
|
||||
|
@ -3,6 +3,24 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<ListPreference
|
||||
android:key="video_renderer"
|
||||
android:title="@string/renderer"
|
||||
android:summary="%s"
|
||||
app:iconSpaceReserved="false"
|
||||
android:entries="@array/video_renderer_options"
|
||||
android:entryValues="@array/video_renderer_values"
|
||||
android:defaultValue="software"/>
|
||||
|
||||
<ListPreference
|
||||
android:key="video_internal_resolution"
|
||||
android:title="@string/internal_resolution"
|
||||
android:summary="%s"
|
||||
app:iconSpaceReserved="false"
|
||||
android:entries="@array/video_internal_resolution_options"
|
||||
android:entryValues="@array/video_internal_resolution_values"
|
||||
android:defaultValue="1"/>
|
||||
|
||||
<ListPreference
|
||||
android:key="video_filtering"
|
||||
android:title="@string/filter"
|
||||
|
@ -1,7 +1,7 @@
|
||||
object AppConfig {
|
||||
const val compileSdkVersion = 34
|
||||
const val targetSdkVersion = compileSdkVersion
|
||||
const val minSdkVersion = 21
|
||||
const val minSdkVersion = 24
|
||||
const val ndkVersion = "25.1.8937393"
|
||||
|
||||
const val versionCode = 30
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit a26a30f19a1d0317af82ebf8074740a61cf3584b
|
||||
Subproject commit f1080b5160142f4b2a3720c02cc14cd808cbc225
|
Loading…
Reference in New Issue
Block a user