mirror of
https://github.com/libretro/ppsspp.git
synced 2024-11-27 10:20:49 +00:00
Merge pull request #10247 from unknownbrackets/ui-tween
UI: Show a loading message during shader preload
This commit is contained in:
commit
cf5ede493b
@ -959,6 +959,8 @@ add_library(native STATIC
|
||||
ext/native/ui/ui_context.h
|
||||
ext/native/ui/ui_screen.cpp
|
||||
ext/native/ui/ui_screen.h
|
||||
ext/native/ui/ui_tween.cpp
|
||||
ext/native/ui/ui_tween.h
|
||||
ext/native/ui/view.cpp
|
||||
ext/native/ui/view.h
|
||||
ext/native/ui/viewgroup.cpp
|
||||
|
@ -436,16 +436,16 @@ bool PSP_InitUpdate(std::string *error_string) {
|
||||
|
||||
bool success = coreParameter.fileToStart != "";
|
||||
*error_string = coreParameter.errorString;
|
||||
if (success) {
|
||||
if (success && gpu == nullptr) {
|
||||
success = GPU_Init(coreParameter.graphicsContext, coreParameter.thin3d);
|
||||
if (!success) {
|
||||
PSP_Shutdown();
|
||||
*error_string = "Unable to initialize rendering engine.";
|
||||
}
|
||||
}
|
||||
pspIsInited = success;
|
||||
pspIsIniting = false;
|
||||
return true;
|
||||
pspIsInited = success && GPU_IsReady();
|
||||
pspIsIniting = success && !pspIsInited;
|
||||
return !success || pspIsInited;
|
||||
}
|
||||
|
||||
bool PSP_Init(const CoreParameter &coreParam, std::string *error_string) {
|
||||
|
@ -171,7 +171,8 @@ GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
|
||||
if (discID.size()) {
|
||||
File::CreateFullPath(GetSysDirectory(DIRECTORY_APP_CACHE));
|
||||
shaderCachePath_ = GetSysDirectory(DIRECTORY_APP_CACHE) + "/" + discID + ".glshadercache";
|
||||
shaderManagerGL_->LoadAndPrecompile(shaderCachePath_);
|
||||
// Actually precompiled by IsReady() since we're single-threaded.
|
||||
shaderManagerGL_->Load(shaderCachePath_);
|
||||
}
|
||||
|
||||
if (g_Config.bHardwareTessellation) {
|
||||
@ -350,6 +351,10 @@ void GPU_GLES::CheckGPUFeatures() {
|
||||
gstate_c.featureFlags = features;
|
||||
}
|
||||
|
||||
bool GPU_GLES::IsReady() {
|
||||
return shaderManagerGL_->ContinuePrecompile();
|
||||
}
|
||||
|
||||
// Let's avoid passing nulls into snprintf().
|
||||
static const char *GetGLStringAlways(GLenum name) {
|
||||
const GLubyte *value = glGetString(name);
|
||||
|
@ -38,6 +38,8 @@ public:
|
||||
// This gets called on startup and when we get back from settings.
|
||||
void CheckGPUFeatures();
|
||||
|
||||
bool IsReady() override;
|
||||
|
||||
void PreExecuteOp(u32 op, u32 diff) override;
|
||||
void ExecuteOp(u32 op, u32 diff) override;
|
||||
|
||||
|
@ -955,7 +955,7 @@ struct CacheHeader {
|
||||
int numLinkedPrograms;
|
||||
};
|
||||
|
||||
void ShaderManagerGLES::LoadAndPrecompile(const std::string &filename) {
|
||||
void ShaderManagerGLES::Load(const std::string &filename) {
|
||||
File::IOFile f(filename, "rb");
|
||||
u64 sz = f.GetSize();
|
||||
if (!f.IsOpen()) {
|
||||
@ -969,7 +969,8 @@ void ShaderManagerGLES::LoadAndPrecompile(const std::string &filename) {
|
||||
return;
|
||||
}
|
||||
time_update();
|
||||
double start = time_now_d();
|
||||
diskCachePending_.start = time_now_d();
|
||||
diskCachePending_.Clear();
|
||||
|
||||
// Sanity check the file contents
|
||||
if (header.numFragmentShaders > 1000 || header.numVertexShaders > 1000 || header.numLinkedPrograms > 1000) {
|
||||
@ -987,42 +988,19 @@ void ShaderManagerGLES::LoadAndPrecompile(const std::string &filename) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < header.numVertexShaders; i++) {
|
||||
VShaderID id;
|
||||
if (!f.ReadArray(&id, 1)) {
|
||||
return;
|
||||
}
|
||||
if (!vsCache_.Get(id)) {
|
||||
if (id.Bit(VS_BIT_IS_THROUGH) && id.Bit(VS_BIT_USE_HW_TRANSFORM)) {
|
||||
// Clearly corrupt, bailing.
|
||||
ERROR_LOG_REPORT(G3D, "Corrupt shader cache: Both IS_THROUGH and USE_HW_TRANSFORM set.");
|
||||
return;
|
||||
}
|
||||
diskCachePending_.vert.resize(header.numVertexShaders);
|
||||
if (!f.ReadArray(&diskCachePending_.vert[0], header.numVertexShaders)) {
|
||||
diskCachePending_.vert.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
Shader *vs = CompileVertexShader(id);
|
||||
if (vs->Failed()) {
|
||||
// Give up on using the cache, just bail. We can't safely create the fallback shaders here
|
||||
// without trying to deduce the vertType from the VSID.
|
||||
ERROR_LOG(G3D, "Failed to compile a vertex shader loading from cache. Skipping rest of shader cache.");
|
||||
delete vs;
|
||||
return;
|
||||
}
|
||||
vsCache_.Insert(id, vs);
|
||||
} else {
|
||||
WARN_LOG(G3D, "Duplicate vertex shader found in GL shader cache, ignoring");
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < header.numFragmentShaders; i++) {
|
||||
FShaderID id;
|
||||
if (!f.ReadArray(&id, 1)) {
|
||||
return;
|
||||
}
|
||||
if (!fsCache_.Get(id)) {
|
||||
fsCache_.Insert(id, CompileFragmentShader(id));
|
||||
} else {
|
||||
WARN_LOG(G3D, "Duplicate fragment shader found in GL shader cache, ignoring");
|
||||
}
|
||||
diskCachePending_.frag.resize(header.numFragmentShaders);
|
||||
if (!f.ReadArray(&diskCachePending_.frag[0], header.numFragmentShaders)) {
|
||||
diskCachePending_.vert.clear();
|
||||
diskCachePending_.frag.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < header.numLinkedPrograms; i++) {
|
||||
VShaderID vsid;
|
||||
FShaderID fsid;
|
||||
@ -1032,6 +1010,76 @@ void ShaderManagerGLES::LoadAndPrecompile(const std::string &filename) {
|
||||
if (!f.ReadArray(&fsid, 1)) {
|
||||
return;
|
||||
}
|
||||
diskCachePending_.link.push_back(std::make_pair(vsid, fsid));
|
||||
}
|
||||
|
||||
// Actual compilation happens in ContinuePrecompile(), called by GPU_GLES's IsReady.
|
||||
NOTICE_LOG(G3D, "Precompiling the shader cache from '%s'", filename.c_str());
|
||||
diskCacheDirty_ = false;
|
||||
}
|
||||
|
||||
bool ShaderManagerGLES::ContinuePrecompile(float sliceTime) {
|
||||
auto &pending = diskCachePending_;
|
||||
if (pending.Done()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
double start = real_time_now();
|
||||
// Let's try to keep it under sliceTime if possible.
|
||||
double end = start + sliceTime;
|
||||
|
||||
for (size_t &i = pending.vertPos; i < pending.vert.size(); i++) {
|
||||
if (real_time_now() >= end) {
|
||||
// We'll finish later.
|
||||
return false;
|
||||
}
|
||||
|
||||
const VShaderID &id = pending.vert[i];
|
||||
if (!vsCache_.Get(id)) {
|
||||
if (id.Bit(VS_BIT_IS_THROUGH) && id.Bit(VS_BIT_USE_HW_TRANSFORM)) {
|
||||
// Clearly corrupt, bailing.
|
||||
ERROR_LOG_REPORT(G3D, "Corrupt shader cache: Both IS_THROUGH and USE_HW_TRANSFORM set.");
|
||||
pending.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
Shader *vs = CompileVertexShader(id);
|
||||
if (vs->Failed()) {
|
||||
// Give up on using the cache, just bail. We can't safely create the fallback shaders here
|
||||
// without trying to deduce the vertType from the VSID.
|
||||
ERROR_LOG(G3D, "Failed to compile a vertex shader loading from cache. Skipping rest of shader cache.");
|
||||
delete vs;
|
||||
pending.Clear();
|
||||
return false;
|
||||
}
|
||||
vsCache_.Insert(id, vs);
|
||||
} else {
|
||||
WARN_LOG(G3D, "Duplicate vertex shader found in GL shader cache, ignoring");
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t &i = pending.fragPos; i < pending.frag.size(); i++) {
|
||||
if (real_time_now() >= end) {
|
||||
// We'll finish later.
|
||||
return false;
|
||||
}
|
||||
|
||||
const FShaderID &id = pending.frag[i];
|
||||
if (!fsCache_.Get(id)) {
|
||||
fsCache_.Insert(id, CompileFragmentShader(id));
|
||||
} else {
|
||||
WARN_LOG(G3D, "Duplicate fragment shader found in GL shader cache, ignoring");
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t &i = pending.linkPos; i < pending.link.size(); i++) {
|
||||
if (real_time_now() >= end) {
|
||||
// We'll finish later.
|
||||
return false;
|
||||
}
|
||||
|
||||
const VShaderID &vsid = pending.link[i].first;
|
||||
const FShaderID &fsid = pending.link[i].second;
|
||||
Shader *vs = vsCache_.Get(vsid);
|
||||
Shader *fs = fsCache_.Get(fsid);
|
||||
if (vs && fs) {
|
||||
@ -1040,12 +1088,15 @@ void ShaderManagerGLES::LoadAndPrecompile(const std::string &filename) {
|
||||
linkedShaderCache_.push_back(entry);
|
||||
}
|
||||
}
|
||||
time_update();
|
||||
double end = time_now_d();
|
||||
|
||||
NOTICE_LOG(G3D, "Compiled and linked %d programs (%d vertex, %d fragment) in %0.1f milliseconds", header.numLinkedPrograms, header.numVertexShaders, header.numFragmentShaders, 1000 * (end - start));
|
||||
NOTICE_LOG(G3D, "Loaded the shader cache from '%s'", filename.c_str());
|
||||
diskCacheDirty_ = false;
|
||||
// Okay, finally done. Time to report status.
|
||||
time_update();
|
||||
double finish = time_now_d();
|
||||
|
||||
NOTICE_LOG(G3D, "Compiled and linked %d programs (%d vertex, %d fragment) in %0.1f milliseconds", (int)pending.link.size(), (int)pending.vert.size(), (int)pending.frag.size(), 1000 * (finish - pending.start));
|
||||
pending.Clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShaderManagerGLES::Save(const std::string &filename) {
|
||||
|
@ -167,7 +167,8 @@ public:
|
||||
std::vector<std::string> DebugGetShaderIDs(DebugShaderType type);
|
||||
std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType);
|
||||
|
||||
void LoadAndPrecompile(const std::string &filename);
|
||||
void Load(const std::string &filename);
|
||||
bool ContinuePrecompile(float sliceTime = 1.0f / 60.0f);
|
||||
void Save(const std::string &filename);
|
||||
|
||||
private:
|
||||
@ -203,4 +204,27 @@ private:
|
||||
VSCache vsCache_;
|
||||
|
||||
bool diskCacheDirty_;
|
||||
struct {
|
||||
std::vector<VShaderID> vert;
|
||||
std::vector<FShaderID> frag;
|
||||
std::vector<std::pair<VShaderID, FShaderID>> link;
|
||||
|
||||
size_t vertPos = 0;
|
||||
size_t fragPos = 0;
|
||||
size_t linkPos = 0;
|
||||
double start;
|
||||
|
||||
void Clear() {
|
||||
vert.clear();
|
||||
frag.clear();
|
||||
link.clear();
|
||||
vertPos = 0;
|
||||
fragPos = 0;
|
||||
linkPos = 0;
|
||||
}
|
||||
|
||||
bool Done() {
|
||||
return vertPos >= vert.size() && fragPos >= frag.size() && linkPos >= link.size();
|
||||
}
|
||||
} diskCachePending_;
|
||||
};
|
||||
|
@ -55,6 +55,12 @@ static void SetGPU(T *obj) {
|
||||
#undef new
|
||||
#endif
|
||||
|
||||
bool GPU_IsReady() {
|
||||
if (gpu)
|
||||
return gpu->IsReady();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GPU_Init(GraphicsContext *ctx, Draw::DrawContext *draw) {
|
||||
#if PPSSPP_PLATFORM(UWP)
|
||||
SetGPU(new GPU_D3D11(ctx, draw));
|
||||
|
@ -105,4 +105,5 @@ namespace Draw {
|
||||
}
|
||||
|
||||
bool GPU_Init(GraphicsContext *ctx, Draw::DrawContext *thin3d);
|
||||
bool GPU_IsReady();
|
||||
void GPU_Shutdown();
|
||||
|
@ -70,6 +70,9 @@ public:
|
||||
Draw::DrawContext *GetDrawContext() override {
|
||||
return draw_;
|
||||
}
|
||||
bool IsReady() override {
|
||||
return true;
|
||||
}
|
||||
void Reinitialize() override;
|
||||
|
||||
void BeginHostFrame() override;
|
||||
|
@ -168,6 +168,7 @@ public:
|
||||
virtual Draw::DrawContext *GetDrawContext() = 0;
|
||||
|
||||
// Initialization
|
||||
virtual bool IsReady() = 0;
|
||||
virtual void InitClear() = 0;
|
||||
virtual void Reinitialize() = 0;
|
||||
|
||||
|
115
UI/EmuScreen.cpp
115
UI/EmuScreen.cpp
@ -28,8 +28,11 @@
|
||||
#include "gfx_es2/draw_text.h"
|
||||
|
||||
#include "input/input_state.h"
|
||||
#include "math/curves.h"
|
||||
#include "ui/ui.h"
|
||||
#include "ui/ui_context.h"
|
||||
#include "ui/ui_tween.h"
|
||||
#include "ui/view.h"
|
||||
#include "i18n/i18n.h"
|
||||
|
||||
#include "Common/KeyMap.h"
|
||||
@ -220,6 +223,9 @@ void EmuScreen::bootGame(const std::string &filename) {
|
||||
I18NCategory *gr = GetI18NCategory("Graphics");
|
||||
host->NotifyUserMessage(gr->T("BlockTransferRequired", "Warning: This game requires Simulate Block Transfer Mode to be set to On."), 15.0f);
|
||||
}
|
||||
|
||||
loadingViewColor_->Divert(0xFFFFFFFF, 0.15f);
|
||||
loadingViewVisible_->Divert(UI::V_VISIBLE, 0.15f);
|
||||
}
|
||||
|
||||
void EmuScreen::bootComplete() {
|
||||
@ -268,6 +274,9 @@ void EmuScreen::bootComplete() {
|
||||
System_SendMessage("event", "startgame");
|
||||
|
||||
saveStateSlot_ = SaveState::GetCurrentSlot();
|
||||
|
||||
loadingViewColor_->Divert(0x00FFFFFF, 0.2f);
|
||||
loadingViewVisible_->Divert(UI::V_INVISIBLE, 0.2f);
|
||||
}
|
||||
|
||||
EmuScreen::~EmuScreen() {
|
||||
@ -344,7 +353,7 @@ void EmuScreen::sendMessage(const char *message, const char *value) {
|
||||
} else {
|
||||
PSP_Shutdown();
|
||||
bootPending_ = true;
|
||||
bootGame(value);
|
||||
gamePath_ = value;
|
||||
}
|
||||
} else if (!strcmp(message, "control mapping") && screenManager()->topScreen() == this) {
|
||||
UpdateUIState(UISTATE_MENU);
|
||||
@ -794,11 +803,15 @@ void EmuScreen::processAxis(const AxisInput &axis, int direction) {
|
||||
|
||||
void EmuScreen::CreateViews() {
|
||||
using namespace UI;
|
||||
|
||||
I18NCategory *sc = GetI18NCategory("Screen");
|
||||
I18NCategory *dev = GetI18NCategory("Developer");
|
||||
|
||||
const Bounds &bounds = screenManager()->getUIContext()->GetBounds();
|
||||
InitPadLayout(bounds.w, bounds.h);
|
||||
root_ = CreatePadLayout(bounds.w, bounds.h, &pauseTrigger_);
|
||||
if (g_Config.bShowDeveloperMenu) {
|
||||
root_->Add(new Button("DevMenu"))->OnClick.Handle(this, &EmuScreen::OnDevTools);
|
||||
root_->Add(new Button(dev->T("DevMenu")))->OnClick.Handle(this, &EmuScreen::OnDevTools);
|
||||
}
|
||||
saveStatePreview_ = new AsyncImageFileView("", IS_FIXED, nullptr, new AnchorLayoutParams(bounds.centerX(), 100, NONE, NONE, true));
|
||||
saveStatePreview_->SetFixedSize(160, 90);
|
||||
@ -807,6 +820,13 @@ void EmuScreen::CreateViews() {
|
||||
saveStatePreview_->SetCanBeFocused(false);
|
||||
root_->Add(saveStatePreview_);
|
||||
root_->Add(new OnScreenMessagesView(new AnchorLayoutParams((Size)bounds.w, (Size)bounds.h)));
|
||||
|
||||
loadingView_ = new TextView(sc->T("Loading game..."), new AnchorLayoutParams(bounds.centerX(), bounds.centerY(), NONE, NONE, true));
|
||||
root_->Add(loadingView_);
|
||||
|
||||
// We start invisible here, in case of recreated views.
|
||||
loadingViewColor_ = loadingView_->AddTween(new TextColorTween(0x00FFFFFF, 0x00FFFFFF, 0.2f, &bezierEaseInOut));
|
||||
loadingViewVisible_ = loadingView_->AddTween(new VisibilityTween(UI::V_INVISIBLE, UI::V_INVISIBLE, 0.2f, &bezierEaseInOut));
|
||||
}
|
||||
|
||||
UI::EventReturn EmuScreen::OnDevTools(UI::EventParams ¶ms) {
|
||||
@ -820,11 +840,11 @@ UI::EventReturn EmuScreen::OnDevTools(UI::EventParams ¶ms) {
|
||||
}
|
||||
|
||||
void EmuScreen::update() {
|
||||
UIScreen::update();
|
||||
|
||||
if (bootPending_)
|
||||
bootGame(gamePath_);
|
||||
|
||||
UIScreen::update();
|
||||
|
||||
// Simply forcibly update to the current screen size every frame. Doesn't cost much.
|
||||
// If bounds is set to be smaller than the actual pixel resolution of the display, respect that.
|
||||
// TODO: Should be able to use g_dpi_scale here instead. Might want to store the dpi scale in the UI context too.
|
||||
@ -1026,6 +1046,7 @@ void EmuScreen::render() {
|
||||
// In this case, we need to double check it here.
|
||||
checkPowerDown();
|
||||
thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR });
|
||||
renderUI();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1072,47 +1093,10 @@ void EmuScreen::render() {
|
||||
if (invalid_)
|
||||
return;
|
||||
|
||||
const bool hasVisibleUI = !osm.IsEmpty() || saveStatePreview_->GetVisibility() != UI::V_GONE || g_Config.bShowTouchControls;
|
||||
const bool hasVisibleUI = !osm.IsEmpty() || saveStatePreview_->GetVisibility() != UI::V_GONE || g_Config.bShowTouchControls || loadingView_->GetVisibility() == UI::V_VISIBLE;
|
||||
const bool showDebugUI = g_Config.bShowDebugStats || g_Config.bShowDeveloperMenu || g_Config.bShowAudioDebug || g_Config.bShowFrameProfiler;
|
||||
if (hasVisibleUI || showDebugUI || g_Config.iShowFPSCounter != 0) {
|
||||
// This sets up some important states but not the viewport.
|
||||
screenManager()->getUIContext()->Begin();
|
||||
|
||||
Viewport viewport;
|
||||
viewport.TopLeftX = 0;
|
||||
viewport.TopLeftY = 0;
|
||||
viewport.Width = pixel_xres;
|
||||
viewport.Height = pixel_yres;
|
||||
viewport.MaxDepth = 1.0;
|
||||
viewport.MinDepth = 0.0;
|
||||
thin3d->SetViewports(1, &viewport);
|
||||
|
||||
DrawBuffer *draw2d = screenManager()->getUIContext()->Draw();
|
||||
|
||||
if (root_) {
|
||||
UI::LayoutViewHierarchy(*screenManager()->getUIContext(), root_);
|
||||
root_->Draw(*screenManager()->getUIContext());
|
||||
}
|
||||
|
||||
if (g_Config.bShowDebugStats) {
|
||||
DrawDebugStats(draw2d);
|
||||
}
|
||||
|
||||
if (g_Config.bShowAudioDebug) {
|
||||
DrawAudioDebugStats(draw2d);
|
||||
}
|
||||
|
||||
if (g_Config.iShowFPSCounter) {
|
||||
DrawFPS(draw2d, screenManager()->getUIContext()->GetBounds());
|
||||
}
|
||||
|
||||
#ifdef USE_PROFILER
|
||||
if (g_Config.bShowFrameProfiler) {
|
||||
DrawProfile(*screenManager()->getUIContext());
|
||||
}
|
||||
#endif
|
||||
|
||||
screenManager()->getUIContext()->End();
|
||||
renderUI();
|
||||
}
|
||||
|
||||
// We have no use for backbuffer depth or stencil, so let tiled renderers discard them after tiling.
|
||||
@ -1133,6 +1117,51 @@ void EmuScreen::render() {
|
||||
*/
|
||||
}
|
||||
|
||||
void EmuScreen::renderUI() {
|
||||
using namespace Draw;
|
||||
|
||||
DrawContext *thin3d = screenManager()->getDrawContext();
|
||||
|
||||
// This sets up some important states but not the viewport.
|
||||
screenManager()->getUIContext()->Begin();
|
||||
|
||||
Viewport viewport;
|
||||
viewport.TopLeftX = 0;
|
||||
viewport.TopLeftY = 0;
|
||||
viewport.Width = pixel_xres;
|
||||
viewport.Height = pixel_yres;
|
||||
viewport.MaxDepth = 1.0;
|
||||
viewport.MinDepth = 0.0;
|
||||
thin3d->SetViewports(1, &viewport);
|
||||
|
||||
DrawBuffer *draw2d = screenManager()->getUIContext()->Draw();
|
||||
|
||||
if (root_) {
|
||||
UI::LayoutViewHierarchy(*screenManager()->getUIContext(), root_);
|
||||
root_->Draw(*screenManager()->getUIContext());
|
||||
}
|
||||
|
||||
if (g_Config.bShowDebugStats && !invalid_) {
|
||||
DrawDebugStats(draw2d);
|
||||
}
|
||||
|
||||
if (g_Config.bShowAudioDebug && !invalid_) {
|
||||
DrawAudioDebugStats(draw2d);
|
||||
}
|
||||
|
||||
if (g_Config.iShowFPSCounter && !invalid_) {
|
||||
DrawFPS(draw2d, screenManager()->getUIContext()->GetBounds());
|
||||
}
|
||||
|
||||
#ifdef USE_PROFILER
|
||||
if (g_Config.bShowFrameProfiler && !invalid_) {
|
||||
DrawProfile(*screenManager()->getUIContext());
|
||||
}
|
||||
#endif
|
||||
|
||||
screenManager()->getUIContext()->End();
|
||||
}
|
||||
|
||||
void EmuScreen::deviceLost() {
|
||||
ILOG("EmuScreen::deviceLost()");
|
||||
if (gpu)
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "input/keycodes.h"
|
||||
#include "ui/screen.h"
|
||||
#include "ui/ui_screen.h"
|
||||
#include "ui/ui_tween.h"
|
||||
#include "Common/KeyMap.h"
|
||||
|
||||
struct AxisInput;
|
||||
@ -56,6 +57,7 @@ protected:
|
||||
private:
|
||||
void bootGame(const std::string &filename);
|
||||
void bootComplete();
|
||||
void renderUI();
|
||||
void processAxis(const AxisInput &axis, int direction);
|
||||
|
||||
void pspKey(int pspKeyCode, int flags);
|
||||
@ -97,4 +99,8 @@ private:
|
||||
double saveStatePreviewShownTime_;
|
||||
AsyncImageFileView *saveStatePreview_;
|
||||
int saveStateSlot_;
|
||||
|
||||
UI::View *loadingView_ = nullptr;
|
||||
UI::TextColorTween *loadingViewColor_ = nullptr;
|
||||
UI::VisibilityTween *loadingViewVisible_ = nullptr;
|
||||
};
|
||||
|
@ -376,6 +376,7 @@
|
||||
<ClInclude Include="..\..\ext\native\ui\ui.h" />
|
||||
<ClInclude Include="..\..\ext\native\ui\ui_context.h" />
|
||||
<ClInclude Include="..\..\ext\native\ui\ui_screen.h" />
|
||||
<ClInclude Include="..\..\ext\native\ui\ui_tween.h" />
|
||||
<ClInclude Include="..\..\ext\native\ui\view.h" />
|
||||
<ClInclude Include="..\..\ext\native\ui\viewgroup.h" />
|
||||
<ClInclude Include="..\..\ext\native\util\const_map.h" />
|
||||
@ -1290,6 +1291,7 @@
|
||||
<ClCompile Include="..\..\ext\native\ui\ui.cpp" />
|
||||
<ClCompile Include="..\..\ext\native\ui\ui_context.cpp" />
|
||||
<ClCompile Include="..\..\ext\native\ui\ui_screen.cpp" />
|
||||
<ClCompile Include="..\..\ext\native\ui\ui_tween.cpp" />
|
||||
<ClCompile Include="..\..\ext\native\ui\view.cpp" />
|
||||
<ClCompile Include="..\..\ext\native\ui\viewgroup.cpp" />
|
||||
<ClCompile Include="..\..\ext\native\util\hash\hash.cpp" />
|
||||
|
@ -154,6 +154,9 @@
|
||||
<ClCompile Include="..\..\ext\native\ui\ui_screen.cpp">
|
||||
<Filter>ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\ext\native\ui\ui_tween.cpp">
|
||||
<Filter>ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\ext\native\ui\view.cpp">
|
||||
<Filter>ui</Filter>
|
||||
</ClCompile>
|
||||
@ -590,6 +593,9 @@
|
||||
<ClInclude Include="..\..\ext\native\ui\ui_screen.h">
|
||||
<Filter>ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\ext\native\ui\ui_tween.h">
|
||||
<Filter>ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\ext\native\ui\view.h">
|
||||
<Filter>ui</Filter>
|
||||
</ClInclude>
|
||||
|
@ -91,6 +91,7 @@ LOCAL_SRC_FILES :=\
|
||||
ui/viewgroup.cpp \
|
||||
ui/ui.cpp \
|
||||
ui/ui_screen.cpp \
|
||||
ui/ui_tween.cpp \
|
||||
ui/ui_context.cpp \
|
||||
ui/screen.cpp \
|
||||
util/text/utf8.cpp \
|
||||
|
@ -1,5 +1,14 @@
|
||||
#include "base/colorutil.h"
|
||||
|
||||
template <typename T>
|
||||
static T clamp(T f, T low, T high) {
|
||||
if (f < low)
|
||||
return low;
|
||||
if (f > high)
|
||||
return high;
|
||||
return f;
|
||||
}
|
||||
|
||||
uint32_t whiteAlpha(float alpha) {
|
||||
if (alpha < 0.0f) alpha = 0.0f;
|
||||
if (alpha > 1.0f) alpha = 1.0f;
|
||||
@ -20,6 +29,20 @@ uint32_t colorAlpha(uint32_t rgb, float alpha) {
|
||||
return ((int)(alpha*255)<<24) | (rgb & 0xFFFFFF);
|
||||
}
|
||||
|
||||
uint32_t colorBlend(uint32_t rgb1, uint32_t rgb2, float alpha) {
|
||||
float invAlpha = (1.0f - alpha);
|
||||
int r = (int)(((rgb1 >> 0) & 0xFF) * alpha + ((rgb2 >> 0) & 0xFF) * invAlpha);
|
||||
int g = (int)(((rgb1 >> 8) & 0xFF) * alpha + ((rgb2 >> 8) & 0xFF) * invAlpha);
|
||||
int b = (int)(((rgb1 >> 16) & 0xFF) * alpha + ((rgb2 >> 16) & 0xFF) * invAlpha);
|
||||
int a = (int)(((rgb1 >> 24) & 0xFF) * alpha + ((rgb2 >> 24) & 0xFF) * invAlpha);
|
||||
|
||||
uint32_t c = clamp(a, 0, 255) << 24;
|
||||
c |= clamp(b, 0, 255) << 16;
|
||||
c |= clamp(g, 0, 255) << 8;
|
||||
c |= clamp(r, 0, 255);
|
||||
return c;
|
||||
}
|
||||
|
||||
uint32_t alphaMul(uint32_t color, float alphaMul) {
|
||||
uint32_t rgb = color & 0xFFFFFF;
|
||||
int32_t alpha = color >> 24;
|
||||
@ -38,17 +61,7 @@ uint32_t rgba(float r, float g, float b, float alpha) {
|
||||
}
|
||||
|
||||
uint32_t rgba_clamp(float r, float g, float b, float a) {
|
||||
if (r > 1.0f) r = 1.0f;
|
||||
if (g > 1.0f) g = 1.0f;
|
||||
if (b > 1.0f) b = 1.0f;
|
||||
if (a > 1.0f) a = 1.0f;
|
||||
|
||||
if (r < 0.0f) r = 0.0f;
|
||||
if (g < 0.0f) g = 0.0f;
|
||||
if (b < 0.0f) b = 0.0f;
|
||||
if (a < 0.0f) a = 0.0f;
|
||||
|
||||
return rgba(r,g,b,a);
|
||||
return rgba(clamp(r, 0.0f, 1.0f), clamp(g, 0.0f, 1.0f), clamp(b, 0.0f, 1.0f), clamp(a, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
/* hsv2rgb.c
|
||||
|
@ -285,6 +285,7 @@
|
||||
<ClInclude Include="ui\ui.h" />
|
||||
<ClInclude Include="ui\ui_context.h" />
|
||||
<ClInclude Include="ui\ui_screen.h" />
|
||||
<ClInclude Include="ui\ui_tween.h" />
|
||||
<ClInclude Include="ui\view.h" />
|
||||
<ClInclude Include="ui\viewgroup.h" />
|
||||
<ClInclude Include="util\random\rng.h" />
|
||||
@ -752,6 +753,7 @@
|
||||
<ClCompile Include="ui\ui.cpp" />
|
||||
<ClCompile Include="ui\ui_context.cpp" />
|
||||
<ClCompile Include="ui\ui_screen.cpp" />
|
||||
<ClCompile Include="ui\ui_tween.cpp" />
|
||||
<ClCompile Include="ui\view.cpp" />
|
||||
<ClCompile Include="ui\viewgroup.cpp" />
|
||||
<ClCompile Include="util\hash\hash.cpp" />
|
||||
|
@ -329,6 +329,9 @@
|
||||
<ClInclude Include="thin3d\DataFormat.h">
|
||||
<Filter>thin3d</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ui\ui_tween.h">
|
||||
<Filter>ui</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="gfx\gl_debug_log.cpp">
|
||||
@ -796,6 +799,9 @@
|
||||
<ClCompile Include="thin3d\VulkanRenderManager.cpp">
|
||||
<Filter>thin3d</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ui\ui_tween.cpp">
|
||||
<Filter>ui</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="gfx">
|
||||
|
38
ext/native/ui/ui_tween.cpp
Normal file
38
ext/native/ui/ui_tween.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
#include "base/colorutil.h"
|
||||
#include "ui/ui_tween.h"
|
||||
#include "ui/view.h"
|
||||
|
||||
namespace UI {
|
||||
|
||||
void Tween::Apply(View *view) {
|
||||
if (time_now() >= start_ + duration_)
|
||||
finishApplied_ = true;
|
||||
|
||||
float pos = Position();
|
||||
DoApply(view, pos);
|
||||
}
|
||||
|
||||
uint32_t ColorTween::Current(float pos) {
|
||||
return colorBlend(to_, from_, pos);
|
||||
}
|
||||
|
||||
void TextColorTween::DoApply(View *view, float pos) {
|
||||
// TODO: No validation without RTTI?
|
||||
TextView *tv = (TextView *)view;
|
||||
tv->SetTextColor(Current(pos));
|
||||
}
|
||||
|
||||
void VisibilityTween::DoApply(View *view, float pos) {
|
||||
view->SetVisibility(Current(pos));
|
||||
}
|
||||
|
||||
Visibility VisibilityTween::Current(float p) {
|
||||
// Prefer V_VISIBLE over V_GONE/V_INVISIBLE.
|
||||
if (from_ == V_VISIBLE && p < 1.0f)
|
||||
return from_;
|
||||
if (to_ == V_VISIBLE && p > 0.0f)
|
||||
return to_;
|
||||
return p >= 1.0f ? to_ : from_;
|
||||
}
|
||||
|
||||
} // namespace
|
139
ext/native/ui/ui_tween.h
Normal file
139
ext/native/ui/ui_tween.h
Normal file
@ -0,0 +1,139 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include "base/timeutil.h"
|
||||
#include "ui/view.h"
|
||||
|
||||
namespace UI {
|
||||
|
||||
// This is the class to use in Update().
|
||||
class Tween {
|
||||
public:
|
||||
Tween(float duration, float (*curve)(float)) : duration_(duration), curve_(curve) {
|
||||
start_ = time_now();
|
||||
}
|
||||
virtual ~Tween() {
|
||||
}
|
||||
|
||||
// Actually apply the tween to a view.
|
||||
void Apply(View *view);
|
||||
|
||||
bool Finished() {
|
||||
return finishApplied_ && time_now() >= start_ + duration_;
|
||||
}
|
||||
|
||||
protected:
|
||||
float DurationOffset() {
|
||||
return time_now() - start_;
|
||||
}
|
||||
|
||||
float Position() {
|
||||
return curve_(std::min(1.0f, DurationOffset() / duration_));
|
||||
}
|
||||
|
||||
virtual void DoApply(View *view, float pos) = 0;
|
||||
|
||||
float start_;
|
||||
float duration_;
|
||||
bool finishApplied_ = false;
|
||||
float (*curve_)(float);
|
||||
};
|
||||
|
||||
// This is the class all tweens inherit from. Shouldn't be used directly, see below.
|
||||
template <typename Value>
|
||||
class TweenBase: public Tween {
|
||||
public:
|
||||
TweenBase(Value from, Value to, float duration, float (*curve)(float) = [](float f) { return f; })
|
||||
: Tween(duration, curve), from_(from), to_(to) {
|
||||
}
|
||||
|
||||
// Use this to change the destination value.
|
||||
// Useful when a state flips while the tween is half-way through.
|
||||
void Divert(const Value &newTo, float newDuration = -1.0f) {
|
||||
const Value newFrom = Current(Position());
|
||||
|
||||
// Are we already part way through another transition?
|
||||
if (time_now() < start_ + duration_) {
|
||||
if (newTo == to_) {
|
||||
// Already on course. Don't change.
|
||||
} else if (newTo == from_) {
|
||||
// Reversing, adjust start_ to be smooth from the current value.
|
||||
float newOffset = duration_ - DurationOffset();
|
||||
if (newDuration >= 0.0f && duration_ > 0.0f) {
|
||||
newOffset *= newDuration / duration_;
|
||||
}
|
||||
start_ = time_now() - newOffset;
|
||||
} else {
|
||||
// Otherwise, start over.
|
||||
start_ = time_now();
|
||||
}
|
||||
} else {
|
||||
// Already finished, so restart.
|
||||
start_ = time_now();
|
||||
finishApplied_ = false;
|
||||
}
|
||||
|
||||
from_ = newFrom;
|
||||
to_ = newTo;
|
||||
if (newDuration >= 0.0f) {
|
||||
duration_ = newDuration;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop animating the value.
|
||||
void Stop() {
|
||||
Reset(Current(Position()));
|
||||
}
|
||||
|
||||
// Use when the value is explicitly reset. Implicitly stops the tween.
|
||||
void Reset(const Value &newFrom) {
|
||||
from_ = newFrom;
|
||||
to_ = newFrom;
|
||||
}
|
||||
|
||||
const Value &FromValue() const {
|
||||
return from_;
|
||||
}
|
||||
const Value &ToValue() const {
|
||||
return to_;
|
||||
}
|
||||
Value CurrentValue() {
|
||||
return Current(Position());
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual Value Current(float pos) = 0;
|
||||
|
||||
Value from_;
|
||||
Value to_;
|
||||
};
|
||||
|
||||
// Generic - subclass this for specific color handling.
|
||||
class ColorTween : public TweenBase<uint32_t> {
|
||||
public:
|
||||
using TweenBase::TweenBase;
|
||||
|
||||
protected:
|
||||
uint32_t Current(float pos) override;
|
||||
};
|
||||
|
||||
class TextColorTween : public ColorTween {
|
||||
public:
|
||||
using ColorTween::ColorTween;
|
||||
|
||||
protected:
|
||||
void DoApply(View *view, float pos) override;
|
||||
};
|
||||
|
||||
class VisibilityTween : public TweenBase<Visibility> {
|
||||
public:
|
||||
using TweenBase::TweenBase;
|
||||
|
||||
protected:
|
||||
void DoApply(View *view, float pos) override;
|
||||
|
||||
Visibility Current(float pos) override;
|
||||
};
|
||||
|
||||
} // namespace
|
@ -12,6 +12,7 @@
|
||||
#include "ui/ui.h"
|
||||
#include "ui/view.h"
|
||||
#include "ui/ui_context.h"
|
||||
#include "ui/ui_tween.h"
|
||||
#include "thin3d/thin3d.h"
|
||||
#include "base/NativeApp.h"
|
||||
|
||||
@ -160,6 +161,18 @@ View::~View() {
|
||||
if (HasFocus())
|
||||
SetFocusedView(0);
|
||||
RemoveQueuedEvents(this);
|
||||
|
||||
// Could use unique_ptr, but then we have to include tween everywhere.
|
||||
for (auto &tween : tweens_)
|
||||
delete tween;
|
||||
tweens_.clear();
|
||||
}
|
||||
|
||||
void View::Update() {
|
||||
for (Tween *tween : tweens_) {
|
||||
if (!tween->Finished())
|
||||
tween->Apply(this);
|
||||
}
|
||||
}
|
||||
|
||||
void View::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
||||
@ -1119,6 +1132,7 @@ void Slider::Draw(UIContext &dc) {
|
||||
}
|
||||
|
||||
void Slider::Update() {
|
||||
View::Update();
|
||||
if (repeat_ >= 0) {
|
||||
repeat_++;
|
||||
}
|
||||
@ -1228,6 +1242,7 @@ void SliderFloat::Draw(UIContext &dc) {
|
||||
}
|
||||
|
||||
void SliderFloat::Update() {
|
||||
View::Update();
|
||||
if (repeat_ >= 0) {
|
||||
repeat_++;
|
||||
}
|
||||
|
@ -224,9 +224,6 @@ struct MeasureSpec {
|
||||
float size;
|
||||
};
|
||||
|
||||
class View;
|
||||
|
||||
|
||||
// Should cover all bases.
|
||||
struct EventParams {
|
||||
View *v;
|
||||
@ -349,6 +346,8 @@ private:
|
||||
|
||||
View *GetFocusedView();
|
||||
|
||||
class Tween;
|
||||
|
||||
class View {
|
||||
public:
|
||||
View(LayoutParams *layoutParams = 0) : layoutParams_(layoutParams), visibility_(V_VISIBLE), measuredWidth_(0), measuredHeight_(0), enabledPtr_(0), enabled_(true), enabledMeansDisabled_(false) {
|
||||
@ -363,7 +362,7 @@ public:
|
||||
virtual bool Key(const KeyInput &input) { return false; }
|
||||
virtual void Touch(const TouchInput &input) {}
|
||||
virtual void Axis(const AxisInput &input) {}
|
||||
virtual void Update() {}
|
||||
virtual void Update();
|
||||
|
||||
// If this view covers these coordinates, it should add itself and its children to the list.
|
||||
virtual void Query(float x, float y, std::vector<View *> &list);
|
||||
@ -424,6 +423,12 @@ public:
|
||||
|
||||
Point GetFocusPosition(FocusDirection dir);
|
||||
|
||||
template <class T>
|
||||
T *AddTween(T *t) {
|
||||
tweens_.push_back(t);
|
||||
return t;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Inputs to layout
|
||||
std::unique_ptr<LayoutParams> layoutParams_;
|
||||
@ -438,6 +443,8 @@ protected:
|
||||
// Outputs of layout. X/Y are absolute screen coordinates, hierarchy is "gone" here.
|
||||
Bounds bounds_;
|
||||
|
||||
std::vector<Tween *> tweens_;
|
||||
|
||||
private:
|
||||
bool *enabledPtr_;
|
||||
bool enabled_;
|
||||
@ -455,7 +462,6 @@ public:
|
||||
bool Key(const KeyInput &input) override { return false; }
|
||||
void Touch(const TouchInput &input) override {}
|
||||
bool CanBeFocused() const override { return false; }
|
||||
void Update() override {}
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user