This commit is contained in:
Niccolò Betto 2023-05-13 15:30:50 +01:00 committed by GitHub
commit 80471a4750
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 271 additions and 39 deletions

View File

@ -189,6 +189,7 @@ dependencies {
/* Other Java */ /* Other Java */
implementation 'info.debatty:java-string-similarity:2.0.0' implementation 'info.debatty:java-string-similarity:2.0.0'
implementation 'com.github.KikiManjaro:colorpicker:v1.1.12' implementation 'com.github.KikiManjaro:colorpicker:v1.1.12'
implementation 'com.github.android:renderscript-intrinsics-replacement-toolkit:344be3f'
} }
kapt { kapt {

View File

@ -414,6 +414,6 @@ namespace skyline::gpu {
if (!*state.settings->disableShaderCache) if (!*state.settings->disableShaderCache)
graphicsPipelineCacheManager.emplace(state, graphicsPipelineCacheManager.emplace(state,
state.os->publicAppFilesPath + "graphics_pipeline_cache/" + titleId); state.os->publicAppFilesPath + "graphics_pipeline_cache/" + titleId);
graphicsPipelineManager.emplace(*this); graphicsPipelineManager.emplace(*this, *state.jvm);
} }
} }

View File

@ -214,6 +214,9 @@ namespace skyline::gpu {
std::scoped_lock lock{mutex}; std::scoped_lock lock{mutex};
compilePendingDescs.erase(pipelineDescIt); compilePendingDescs.erase(pipelineDescIt);
if (compilationCallback)
compilationCallback();
return pipeline; return pipeline;
} }
@ -252,4 +255,15 @@ namespace skyline::gpu {
SerialisePipelineCache(gpu, pipelineCacheDir, rawData); SerialisePipelineCache(gpu, pipelineCacheDir, rawData);
}); });
} }
void GraphicsPipelineAssembler::RegisterCompilationCallback(std::function<void()> callback) {
if (compilationCallback)
throw exception("A compilation callback is already registered");
compilationCallback = std::move(callback);
}
void GraphicsPipelineAssembler::UnregisterCompilationCallback() {
compilationCallback = {};
}
} }

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include <functional>
#include <future> #include <future>
#include <BS_thread_pool.hpp> #include <BS_thread_pool.hpp>
#include <vulkan/vulkan_raii.hpp> #include <vulkan/vulkan_raii.hpp>
@ -59,6 +60,7 @@ namespace skyline::gpu {
vk::raii::PipelineCache vkPipelineCache; //!< A Vulkan Pipeline Cache which stores all unique graphics pipelines vk::raii::PipelineCache vkPipelineCache; //!< A Vulkan Pipeline Cache which stores all unique graphics pipelines
BS::thread_pool pool; BS::thread_pool pool;
std::string pipelineCacheDir; std::string pipelineCacheDir;
std::function<void()> compilationCallback;
/** /**
* @brief All unique metadata in a single attachment for a compatible render pass according to Render Pass Compatibility clause in the Vulkan specification * @brief All unique metadata in a single attachment for a compatible render pass according to Render Pass Compatibility clause in the Vulkan specification
@ -158,5 +160,15 @@ namespace skyline::gpu {
* @brief Saves the current Vulkan pipeline cache to the filesystem * @brief Saves the current Vulkan pipeline cache to the filesystem
*/ */
void SavePipelineCache(); void SavePipelineCache();
/**
* @brief Registers a callback that is called whenever a pipeline is compiled
*/
void RegisterCompilationCallback(std::function<void()> callback);
/**
* @brief Unregisters the compilation callback
*/
void UnregisterCompilationCallback();
}; };
} }

View File

@ -9,6 +9,7 @@
#include <gpu/graphics_pipeline_assembler.h> #include <gpu/graphics_pipeline_assembler.h>
#include <gpu/shader_manager.h> #include <gpu/shader_manager.h>
#include <gpu.h> #include <gpu.h>
#include <jvm.h>
#include <vulkan/vulkan_enums.hpp> #include <vulkan/vulkan_enums.hpp>
#include "graphics_pipeline_state_accessor.h" #include "graphics_pipeline_state_accessor.h"
#include "pipeline_manager.h" #include "pipeline_manager.h"
@ -942,12 +943,19 @@ namespace skyline::gpu::interconnect::maxwell3d {
}); });
} }
PipelineManager::PipelineManager(GPU &gpu) { PipelineManager::PipelineManager(GPU &gpu, JvmManager &jvm) {
if (!gpu.graphicsPipelineCacheManager) if (!gpu.graphicsPipelineCacheManager)
return; return;
std::ifstream stream{gpu.graphicsPipelineCacheManager->OpenReadStream()}; std::atomic<u32> compiledCount{};
auto [stream, totalPipelineCount]{gpu.graphicsPipelineCacheManager->OpenReadStream()};
i64 lastKnownGoodOffset{stream.tellg()}; i64 lastKnownGoodOffset{stream.tellg()};
jvm.ShowPipelineLoadingScreen(totalPipelineCount);
gpu.graphicsPipelineAssembler->RegisterCompilationCallback([&]() {
jvm.UpdatePipelineLoadingProgress(++compiledCount);
});
try { try {
auto startTime{util::GetTimeNs()}; auto startTime{util::GetTimeNs()};
PipelineStateBundle bundle; PipelineStateBundle bundle;
@ -986,8 +994,10 @@ namespace skyline::gpu::interconnect::maxwell3d {
} catch (const exception &e) { } catch (const exception &e) {
Logger::Warn("Pipeline cache corrupted at: 0x{:X}, error: {}", lastKnownGoodOffset, e.what()); Logger::Warn("Pipeline cache corrupted at: 0x{:X}, error: {}", lastKnownGoodOffset, e.what());
gpu.graphicsPipelineCacheManager->InvalidateAllAfter(static_cast<u64>(lastKnownGoodOffset)); gpu.graphicsPipelineCacheManager->InvalidateAllAfter(static_cast<u64>(lastKnownGoodOffset));
return;
} }
gpu.graphicsPipelineAssembler->UnregisterCompilationCallback();
jvm.HidePipelineLoadingScreen();
} }
Pipeline *PipelineManager::FindOrCreate(InterconnectContext &ctx, Textures &textures, ConstantBufferSet &constantBuffers, const PackedPipelineState &packedState, const std::array<ShaderBinary, engine::PipelineCount> &shaderBinaries) { Pipeline *PipelineManager::FindOrCreate(InterconnectContext &ctx, Textures &textures, ConstantBufferSet &constantBuffers, const PackedPipelineState &packedState, const std::array<ShaderBinary, engine::PipelineCount> &shaderBinaries) {

View File

@ -272,7 +272,7 @@ namespace skyline::gpu::interconnect::maxwell3d {
#endif #endif
public: public:
PipelineManager(GPU &gpu); PipelineManager(GPU &gpu, JvmManager &jvm);
Pipeline *FindOrCreate(InterconnectContext &ctx, Textures &textures, ConstantBufferSet &constantBuffers, const PackedPipelineState &packedState, const std::array<ShaderBinary, engine::PipelineCount> &shaderBinaries); Pipeline *FindOrCreate(InterconnectContext &ctx, Textures &textures, ConstantBufferSet &constantBuffers, const PackedPipelineState &packedState, const std::array<ShaderBinary, engine::PipelineCount> &shaderBinaries);
}; };

View File

@ -8,19 +8,26 @@
namespace skyline::gpu { namespace skyline::gpu {
struct PipelineCacheFileHeader { struct PipelineCacheFileHeader {
static constexpr u32 Magic{util::MakeMagic<u32>("PCHE")}; //!< The magic value used to identify a pipeline cache file static constexpr u32 Magic{util::MakeMagic<u32>("PCHE")}; //!< The magic value used to identify a pipeline cache file
static constexpr u32 Version{2}; //!< The version of the pipeline cache file format, MUST be incremented for any format changes static constexpr u32 Version{3}; //!< The version of the pipeline cache file format, MUST be incremented for any format changes
u32 magic{Magic}; u32 magic{Magic};
u32 version{Version}; u32 version{Version};
u32 count{0}; //!< The total number of pipeline cache bundles in the file
auto operator<=>(const PipelineCacheFileHeader &) const = default; auto operator<=>(const PipelineCacheFileHeader &) const = default;
};
static constexpr PipelineCacheFileHeader ValidPipelineCacheFileHeader{}; /**
* @brief Checks if the header is valid
*/
bool IsValid() {
return magic == Magic && version == Version;
}
};
void PipelineCacheManager::Run() { void PipelineCacheManager::Run() {
std::ofstream stream{stagingPath, std::ios::binary | std::ios::trunc}; std::ofstream stream{stagingPath, std::ios::binary | std::ios::trunc};
stream.write(reinterpret_cast<const char *>(&ValidPipelineCacheFileHeader), sizeof(PipelineCacheFileHeader)); PipelineCacheFileHeader header{};
stream.write(reinterpret_cast<const char *>(&header), sizeof(PipelineCacheFileHeader));
while (true) { while (true) {
std::unique_lock lock(writeMutex); std::unique_lock lock(writeMutex);
@ -31,7 +38,15 @@ namespace skyline::gpu {
auto bundle{std::move(writeQueue.front())}; auto bundle{std::move(writeQueue.front())};
writeQueue.pop(); writeQueue.pop();
lock.unlock(); lock.unlock();
bundle->Serialise(stream); bundle->Serialise(stream);
header.count++;
// Rewrite the header with the updated count
auto savedPosition{stream.tellp()};
stream.seekp(0, std::ios_base::beg);
stream.write(reinterpret_cast<const char *>(&header), sizeof(PipelineCacheFileHeader));
stream.seekp(savedPosition);
} }
} }
@ -40,8 +55,8 @@ namespace skyline::gpu {
return false; return false;
PipelineCacheFileHeader header{}; PipelineCacheFileHeader header{};
stream.read(reinterpret_cast<char *>(&header), sizeof(header)); stream.read(reinterpret_cast<char *>(&header), sizeof(PipelineCacheFileHeader));
return header == ValidPipelineCacheFileHeader; return header.IsValid();
} }
void PipelineCacheManager::MergeStaging() { void PipelineCacheManager::MergeStaging() {
@ -49,12 +64,24 @@ namespace skyline::gpu {
if (stagingStream.fail()) if (stagingStream.fail())
return; // If the staging file doesn't exist then there's nothing to merge return; // If the staging file doesn't exist then there's nothing to merge
if (!ValidateHeader(stagingStream)) { PipelineCacheFileHeader stagingHeader{};
stagingStream.read(reinterpret_cast<char *>(&stagingHeader), sizeof(PipelineCacheFileHeader));
if (!stagingHeader.IsValid()) {
Logger::Warn("Discarding invalid pipeline cache staging file"); Logger::Warn("Discarding invalid pipeline cache staging file");
return; return;
} }
std::ofstream mainStream{mainPath, std::ios::binary | std::ios::app}; std::fstream mainStream{mainPath, std::ios::binary | std::ios::in | std::ios::out};
PipelineCacheFileHeader mainHeader{};
mainStream.seekg(0, std::ios_base::beg);
mainStream.read(reinterpret_cast<char *>(&mainHeader), sizeof(PipelineCacheFileHeader));
// Update the main header with the new count
mainHeader.count += stagingHeader.count;
mainStream.seekp(0, std::ios_base::beg);
mainStream.write(reinterpret_cast<const char *>(&mainHeader), sizeof(PipelineCacheFileHeader));
mainStream.seekp(0, std::ios::end);
mainStream << stagingStream.rdbuf(); mainStream << stagingStream.rdbuf();
} }
@ -73,7 +100,8 @@ namespace skyline::gpu {
if (!didExist) { // If the main file didn't exist we need to write the header if (!didExist) { // If the main file didn't exist we need to write the header
std::filesystem::create_directories(std::filesystem::path{mainPath}.parent_path()); std::filesystem::create_directories(std::filesystem::path{mainPath}.parent_path());
std::ofstream mainStream{mainPath, std::ios::binary | std::ios::app}; std::ofstream mainStream{mainPath, std::ios::binary | std::ios::app};
mainStream.write(reinterpret_cast<const char *>(&ValidPipelineCacheFileHeader), sizeof(PipelineCacheFileHeader)); PipelineCacheFileHeader header{};
mainStream.write(reinterpret_cast<const char *>(&header), sizeof(PipelineCacheFileHeader));
} }
// Merge any staging changes into the main file before starting the writer thread // Merge any staging changes into the main file before starting the writer thread
@ -87,12 +115,17 @@ namespace skyline::gpu {
writeCondition.notify_one(); writeCondition.notify_one();
} }
std::ifstream PipelineCacheManager::OpenReadStream() { std::pair<std::ifstream, u32> PipelineCacheManager::OpenReadStream() {
auto mainStream{std::ifstream{mainPath, std::ios::binary}}; auto mainStream{std::ifstream{mainPath, std::ios::binary}};
if (!ValidateHeader(mainStream)) if (mainStream.fail())
throw exception("Pipeline cache main file missing at runtime!");
PipelineCacheFileHeader header{};
mainStream.read(reinterpret_cast<char *>(&header), sizeof(PipelineCacheFileHeader));
if (!header.IsValid())
throw exception("Pipeline cache main file corrupted at runtime!"); throw exception("Pipeline cache main file corrupted at runtime!");
return mainStream; return {std::move(mainStream), header.count};
} }
void PipelineCacheManager::InvalidateAllAfter(u64 offset) { void PipelineCacheManager::InvalidateAllAfter(u64 offset) {

View File

@ -34,7 +34,11 @@ namespace skyline::gpu {
*/ */
void QueueWrite(std::unique_ptr<interconnect::PipelineStateBundle> bundle); void QueueWrite(std::unique_ptr<interconnect::PipelineStateBundle> bundle);
std::ifstream OpenReadStream(); /**
* @brief Opens the main pipeline cache file for reading
* @return A pair containing the stream and the total pipeline count
*/
std::pair<std::ifstream, u32> OpenReadStream();
/** /**
* @brief Shrinks the pipeline cache file to `offset` bytes, removing any (potentially invalid) data after that point * @brief Shrinks the pipeline cache file to `offset` bytes, removing any (potentially invalid) data after that point

View File

@ -63,8 +63,11 @@ namespace skyline {
waitForSubmitOrCancelId{environ->GetMethodID(instanceClass, "waitForSubmitOrCancel", "(Lemu/skyline/applet/swkbd/SoftwareKeyboardDialog;)[Ljava/lang/Object;")}, waitForSubmitOrCancelId{environ->GetMethodID(instanceClass, "waitForSubmitOrCancel", "(Lemu/skyline/applet/swkbd/SoftwareKeyboardDialog;)[Ljava/lang/Object;")},
closeKeyboardId{environ->GetMethodID(instanceClass, "closeKeyboard", "(Lemu/skyline/applet/swkbd/SoftwareKeyboardDialog;)V")}, closeKeyboardId{environ->GetMethodID(instanceClass, "closeKeyboard", "(Lemu/skyline/applet/swkbd/SoftwareKeyboardDialog;)V")},
showValidationResultId{environ->GetMethodID(instanceClass, "showValidationResult", "(Lemu/skyline/applet/swkbd/SoftwareKeyboardDialog;ILjava/lang/String;)I")}, showValidationResultId{environ->GetMethodID(instanceClass, "showValidationResult", "(Lemu/skyline/applet/swkbd/SoftwareKeyboardDialog;ILjava/lang/String;)I")},
getVersionCodeId{environ->GetMethodID(instanceClass, "getVersionCode", "()I")}, getIntegerValueId{environ->GetMethodID(environ->FindClass("java/lang/Integer"), "intValue", "()I")},
getIntegerValueId{environ->GetMethodID(environ->FindClass("java/lang/Integer"), "intValue", "()I")} { showPipelineLoadingScreenId{environ->GetMethodID(instanceClass, "showPipelineLoadingScreen", "(I)V")},
updatePipelineLoadingProgressId{environ->GetMethodID(instanceClass, "updatePipelineLoadingProgress", "(I)V")},
hidePipelineLoadingScreenId{environ->GetMethodID(instanceClass, "hidePipelineLoadingScreen", "()V")},
getVersionCodeId{environ->GetMethodID(instanceClass, "getVersionCode", "()I")} {
env.Initialize(environ); env.Initialize(environ);
} }
@ -137,14 +140,26 @@ namespace skyline {
env->DeleteGlobalRef(dialog); env->DeleteGlobalRef(dialog);
} }
i32 JvmManager::GetVersionCode() {
return env->CallIntMethod(instance, getVersionCodeId);
}
JvmManager::KeyboardCloseResult JvmManager::ShowValidationResult(jobject dialog, KeyboardTextCheckResult checkResult, std::u16string message) { JvmManager::KeyboardCloseResult JvmManager::ShowValidationResult(jobject dialog, KeyboardTextCheckResult checkResult, std::u16string message) {
auto str{env->NewString(reinterpret_cast<const jchar *>(message.data()), static_cast<int>(message.length()))}; auto str{env->NewString(reinterpret_cast<const jchar *>(message.data()), static_cast<int>(message.length()))};
auto result{static_cast<KeyboardCloseResult>(env->CallIntMethod(instance, showValidationResultId, dialog, checkResult, str))}; auto result{static_cast<KeyboardCloseResult>(env->CallIntMethod(instance, showValidationResultId, dialog, checkResult, str))};
env->DeleteLocalRef(str); env->DeleteLocalRef(str);
return result; return result;
} }
void JvmManager::ShowPipelineLoadingScreen(u32 totalPipelineCount) {
env->CallVoidMethod(instance, showPipelineLoadingScreenId, static_cast<jint>(totalPipelineCount));
}
void JvmManager::UpdatePipelineLoadingProgress(u32 progress) {
env->CallVoidMethod(instance, updatePipelineLoadingProgressId, static_cast<jint>(progress));
}
void JvmManager::HidePipelineLoadingScreen() {
env->CallVoidMethod(instance, hidePipelineLoadingScreenId);
}
i32 JvmManager::GetVersionCode() {
return env->CallIntMethod(instance, getVersionCodeId);
}
} }

View File

@ -176,6 +176,21 @@ namespace skyline {
*/ */
KeyboardCloseResult ShowValidationResult(KeyboardHandle dialog, KeyboardTextCheckResult checkResult, std::u16string message); KeyboardCloseResult ShowValidationResult(KeyboardHandle dialog, KeyboardTextCheckResult checkResult, std::u16string message);
/**
* @brief A call to EmulationActivity.showPipelineLoadingScreen in Kotlin
*/
void ShowPipelineLoadingScreen(u32 totalPipelineCount);
/**
* @brief A call to EmulationActivity.updatePipelineLoadingProgress in Kotlin
*/
void UpdatePipelineLoadingProgress(u32 progress);
/**
* @brief A call to EmulationActivity.hidePipelineLoadingScreen in Kotlin
*/
void HidePipelineLoadingScreen();
/** /**
* @brief A call to EmulationActivity.getVersionCode in Kotlin * @brief A call to EmulationActivity.getVersionCode in Kotlin
* @return A version code in Vulkan's format with 14-bit patch + 10-bit major and minor components * @return A version code in Vulkan's format with 14-bit patch + 10-bit major and minor components
@ -186,12 +201,17 @@ namespace skyline {
jmethodID initializeControllersId; jmethodID initializeControllersId;
jmethodID vibrateDeviceId; jmethodID vibrateDeviceId;
jmethodID clearVibrationDeviceId; jmethodID clearVibrationDeviceId;
jmethodID showKeyboardId; jmethodID showKeyboardId;
jmethodID waitForSubmitOrCancelId; jmethodID waitForSubmitOrCancelId;
jmethodID closeKeyboardId; jmethodID closeKeyboardId;
jmethodID showValidationResultId; jmethodID showValidationResultId;
jmethodID getVersionCodeId;
jmethodID getIntegerValueId; jmethodID getIntegerValueId;
jmethodID showPipelineLoadingScreenId;
jmethodID updatePipelineLoadingProgressId;
jmethodID hidePipelineLoadingScreenId;
jmethodID getVersionCodeId;
}; };
} }

View File

@ -32,12 +32,12 @@ import androidx.core.view.updateMargins
import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.FragmentTransaction
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.BuildConfig
import emu.skyline.applet.swkbd.SoftwareKeyboardConfig import emu.skyline.applet.swkbd.SoftwareKeyboardConfig
import emu.skyline.applet.swkbd.SoftwareKeyboardDialog import emu.skyline.applet.swkbd.SoftwareKeyboardDialog
import emu.skyline.data.AppItem import emu.skyline.data.AppItem
import emu.skyline.data.AppItemTag import emu.skyline.data.AppItemTag
import emu.skyline.databinding.EmuActivityBinding import emu.skyline.databinding.EmuActivityBinding
import emu.skyline.emulation.PipelineLoadingFragment
import emu.skyline.input.* import emu.skyline.input.*
import emu.skyline.loader.RomFile import emu.skyline.loader.RomFile
import emu.skyline.loader.getRomFormat import emu.skyline.loader.getRomFormat
@ -447,7 +447,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
} }
} }
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) { override fun onPictureInPictureModeChanged(isInPictureInPictureMode : Boolean, newConfig : Configuration) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (isInPictureInPictureMode) { if (isInPictureInPictureMode) {
@ -464,10 +464,11 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
} else { } else {
try { try {
unregisterReceiver(pictureInPictureReceiver) unregisterReceiver(pictureInPictureReceiver)
} catch (ignored : Exception) { } } catch (ignored : Exception) {
}
resumeEmulator() resumeEmulator()
binding.onScreenControllerView.apply { binding.onScreenControllerView.apply {
controllerType = inputHandler.getFirstControllerType() controllerType = inputHandler.getFirstControllerType()
isGone = controllerType == ControllerType.None || !appSettings.onScreenControl isGone = controllerType == ControllerType.None || !appSettings.onScreenControl
@ -512,6 +513,44 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
vibrators.clear() vibrators.clear()
} }
private lateinit var pipelineLoadingFragment : PipelineLoadingFragment
/**
* Called by native code to show the pipeline loading progress screen
*/
@Suppress("unused")
fun showPipelineLoadingScreen(totalPipelineCount : Int) {
pipelineLoadingFragment = PipelineLoadingFragment.newInstance(item, totalPipelineCount)
supportFragmentManager
.beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out)
.replace(R.id.emulation_fragment, pipelineLoadingFragment)
.commit()
}
/**
* Called by native code to update the pipeline loading progress
*/
@Suppress("unused")
fun updatePipelineLoadingProgress(progress : Int) {
runOnUiThread {
pipelineLoadingFragment.updateProgress(progress)
}
}
/**
* Called by native code to hide the pipeline loading progress screen
*/
@Suppress("unused")
fun hidePipelineLoadingScreen() {
supportFragmentManager
.beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out)
.remove(pipelineLoadingFragment)
.commit()
}
override fun surfaceCreated(holder : SurfaceHolder) { override fun surfaceCreated(holder : SurfaceHolder) {
Log.d(Tag, "surfaceCreated Holder: $holder") Log.d(Tag, "surfaceCreated Holder: $holder")

View File

@ -0,0 +1,79 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.emulation
import android.annotation.SuppressLint
import android.graphics.RenderEffect
import android.graphics.Shader
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.renderscript.Toolkit
import emu.skyline.data.AppItem
import emu.skyline.data.AppItemTag
import emu.skyline.databinding.PipelineLoadingBinding
import emu.skyline.utils.serializable
private const val TotalPipelineCountTag = "PipelineLoadingFragment::TotalCount"
private const val PipelineProgressTag = "PipelineLoadingFragment::Progress"
class PipelineLoadingFragment : Fragment() {
private val item by lazy { requireArguments().serializable<AppItem>(AppItemTag)!! }
private val totalPipelineCount by lazy { requireArguments().getInt(TotalPipelineCountTag) }
private lateinit var binding : PipelineLoadingBinding
override fun onCreateView(inflater : LayoutInflater, container : ViewGroup?, savedInstanceState : Bundle?) = PipelineLoadingBinding.inflate(inflater).also { binding = it }.root
@SuppressLint("SetTextI18n")
override fun onViewCreated(view : View, savedInstanceState : Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.gameTitle.apply {
text = item.title
isSelected = true
}
binding.gameVersion.text = item.version
binding.gameIcon.setImageBitmap(item.bitmapIcon)
val progress = savedInstanceState?.getInt(PipelineProgressTag) ?: 0
binding.progressBar.max = totalPipelineCount
updateProgress(progress)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
binding.backgroundImage.setImageBitmap(item.bitmapIcon)
binding.backgroundImage.setRenderEffect(RenderEffect.createBlurEffect(75f, 75f, Shader.TileMode.MIRROR))
} else {
binding.backgroundImage.setImageBitmap(Toolkit.blur(item.bitmapIcon, 15))
}
}
override fun onSaveInstanceState(outState : Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(PipelineProgressTag, binding.progressBar.progress)
}
@SuppressLint("SetTextI18n")
fun updateProgress(progress : Int) {
if (!this::binding.isInitialized)
return
binding.progressBar.progress = progress
binding.progressLabel.text = "$progress/$totalPipelineCount (${(progress.toFloat() / totalPipelineCount * 100).toInt()}%)"
}
companion object {
fun newInstance(item : AppItem, totalPipelineCount : Int) = PipelineLoadingFragment().apply {
arguments = Bundle().apply {
putSerializable(AppItemTag, item)
putInt(TotalPipelineCountTag, totalPipelineCount)
}
}
}
}

View File

@ -27,8 +27,8 @@
android:layout_gravity="top|left" android:layout_gravity="top|left"
android:layout_marginLeft="@dimen/onScreenItemHorizontalMargin" android:layout_marginLeft="@dimen/onScreenItemHorizontalMargin"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
tools:text="60 FPS\n16.6±0.10ms" android:textColor="@color/colorPerfStatsPrimary"
android:textColor="@color/colorPerfStatsPrimary" /> tools:text="60 FPS\n16.6±0.10ms" />
<ImageButton <ImageButton
android:id="@+id/on_screen_pause_toggle" android:id="@+id/on_screen_pause_toggle"
@ -53,4 +53,9 @@
android:src="@drawable/ic_show" android:src="@drawable/ic_show"
app:tint="#40FFFFFF" app:tint="#40FFFFFF"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<FrameLayout
android:id="@+id/emulation_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout> </FrameLayout>

View File

@ -6,7 +6,7 @@
android:layout_height="match_parent"> android:layout_height="match_parent">
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/game_icon_bg" android:id="@+id/background_image"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:contentDescription="@string/icon" android:contentDescription="@string/icon"
@ -52,7 +52,7 @@
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:maxWidth="360dp" android:maxWidth="360dp"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@+id/shader_info" app:layout_constraintBottom_toTopOf="@+id/pipeline_info"
app:layout_constraintEnd_toEndOf="@id/guidelineTextEdge" app:layout_constraintEnd_toEndOf="@id/guidelineTextEdge"
app:layout_constraintStart_toEndOf="@id/guidelineIconText" app:layout_constraintStart_toEndOf="@id/guidelineIconText"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -80,7 +80,7 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/shader_info" android:id="@+id/pipeline_info"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
@ -90,11 +90,11 @@
app:layout_constraintTop_toBottomOf="@+id/game_info"> app:layout_constraintTop_toBottomOf="@+id/game_info">
<TextView <TextView
android:id="@+id/shaders_compiling" android:id="@+id/pipelines_compiling"
style="?attr/textAppearanceBodyMedium" style="?attr/textAppearanceBodyMedium"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/compiling_cached_shaders" android:text="@string/compiling_cached_pipelines"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="17sp" android:textSize="17sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -109,7 +109,7 @@
app:trackThickness="10dp" /> app:trackThickness="10dp" />
<TextView <TextView
android:id="@+id/progress" android:id="@+id/progress_label"
style="?attr/textAppearanceBodyMedium" style="?attr/textAppearanceBodyMedium"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -274,7 +274,7 @@
<!-- Software Keyboard --> <!-- Software Keyboard -->
<string name="input_hint">Input Text</string> <string name="input_hint">Input Text</string>
<!-- Misc --> <!-- Misc -->
<string name="compiling_cached_shaders">Compiling cached shaders...</string> <string name="compiling_cached_pipelines">Compiling cached pipelines...</string>
<!--suppress AndroidLintUnusedResources --> <!--suppress AndroidLintUnusedResources -->
<string name="expand_button_title" tools:override="true">Expand</string> <string name="expand_button_title" tools:override="true">Expand</string>
<string name="undo">Undo</string> <string name="undo">Undo</string>