From 7ab3e58deccc3efc6ff3a26ed8be74b1689231b4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Dec 2017 09:39:28 -0800 Subject: [PATCH 1/9] UI: Add colorBlend() helper. --- ext/native/base/colorutil.cpp | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/ext/native/base/colorutil.cpp b/ext/native/base/colorutil.cpp index 9accdda53c..bdb4bdbcb1 100644 --- a/ext/native/base/colorutil.cpp +++ b/ext/native/base/colorutil.cpp @@ -1,5 +1,14 @@ #include "base/colorutil.h" +template +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 From b4b850bce86e15656eaec12fee4c73cde6d04a06 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Dec 2017 09:39:50 -0800 Subject: [PATCH 2/9] UI: Add a simple tween class. --- CMakeLists.txt | 2 + UWP/NativeUWP/NativeUWP.vcxproj | 2 + UWP/NativeUWP/NativeUWP.vcxproj.filters | 6 ++ ext/native/Android.mk | 1 + ext/native/native.vcxproj | 2 + ext/native/native.vcxproj.filters | 6 ++ ext/native/ui/ui_tween.cpp | 31 +++++++ ext/native/ui/ui_tween.h | 118 ++++++++++++++++++++++++ 8 files changed, 168 insertions(+) create mode 100644 ext/native/ui/ui_tween.cpp create mode 100644 ext/native/ui/ui_tween.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 889bd439a0..1008c5566e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -955,6 +955,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 diff --git a/UWP/NativeUWP/NativeUWP.vcxproj b/UWP/NativeUWP/NativeUWP.vcxproj index f107dbabb7..9db58ee9a8 100644 --- a/UWP/NativeUWP/NativeUWP.vcxproj +++ b/UWP/NativeUWP/NativeUWP.vcxproj @@ -376,6 +376,7 @@ + @@ -1290,6 +1291,7 @@ + diff --git a/UWP/NativeUWP/NativeUWP.vcxproj.filters b/UWP/NativeUWP/NativeUWP.vcxproj.filters index c7e48c5f7a..d0abe95c90 100644 --- a/UWP/NativeUWP/NativeUWP.vcxproj.filters +++ b/UWP/NativeUWP/NativeUWP.vcxproj.filters @@ -154,6 +154,9 @@ ui + + ui + ui @@ -590,6 +593,9 @@ ui + + ui + ui diff --git a/ext/native/Android.mk b/ext/native/Android.mk index ad1271daba..955fd84d87 100644 --- a/ext/native/Android.mk +++ b/ext/native/Android.mk @@ -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 \ diff --git a/ext/native/native.vcxproj b/ext/native/native.vcxproj index aa3b268a19..751ab22e99 100644 --- a/ext/native/native.vcxproj +++ b/ext/native/native.vcxproj @@ -285,6 +285,7 @@ + @@ -752,6 +753,7 @@ + diff --git a/ext/native/native.vcxproj.filters b/ext/native/native.vcxproj.filters index 6996479a53..73e468f8d9 100644 --- a/ext/native/native.vcxproj.filters +++ b/ext/native/native.vcxproj.filters @@ -329,6 +329,9 @@ thin3d + + ui + @@ -796,6 +799,9 @@ thin3d + + ui + diff --git a/ext/native/ui/ui_tween.cpp b/ext/native/ui/ui_tween.cpp new file mode 100644 index 0000000000..49be6ba10d --- /dev/null +++ b/ext/native/ui/ui_tween.cpp @@ -0,0 +1,31 @@ +#include "base/colorutil.h" +#include "ui/ui_tween.h" +#include "ui/view.h" + +namespace UI { + +uint32_t ColorTween::Current() { + return colorBlend(to_, from_, Position()); +} + +void TextColorTween::Apply(View *view) { + // TODO: No validation without RTTI? + TextView *tv = (TextView *)view; + tv->SetTextColor(Current()); +} + +void VisibilityTween::Apply(View *view) { + view->SetVisibility(Current()); +} + +Visibility VisibilityTween::Current() { + // Prefer V_VISIBLE over V_GONE/V_INVISIBLE. + float p = Position(); + 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 diff --git a/ext/native/ui/ui_tween.h b/ext/native/ui/ui_tween.h new file mode 100644 index 0000000000..70d1ad852c --- /dev/null +++ b/ext/native/ui/ui_tween.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include +#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. + virtual void Apply(View *view) = 0; + + bool Finished() { + return time_now() >= start_ + duration_; + } + +protected: + float DurationOffset() { + return time_now() - start_; + } + + float Position() { + return curve_(std::min(1.0f, DurationOffset() / duration_)); + } + + float start_; + float duration_; + float (*curve_)(float); +}; + +// This is the class all tweens inherit from. Shouldn't be used directly, see below. +template +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) { + const Value newFrom = Current(); + + // Are we already part way through another transition? + if (!Finished()) { + 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(); + start_ = time_now() - newOffset; + } else { + // Otherwise, start over. + start_ = time_now(); + } + } else { + // Already finished, so restart. + start_ = time_now(); + } + + from_ = newFrom; + to_ = newTo; + } + + // Stop animating the value. + void Stop() { + Reset(Current()); + } + + // Use when the value is explicitly reset. Implicitly stops the tween. + void Reset(const Value &newFrom) { + from_ = newFrom; + to_ = newFrom; + } + +protected: + virtual Value Current() = 0; + + Value from_; + Value to_; +}; + +// Generic - subclass this for specific color handling. +class ColorTween : public TweenBase { +public: + using TweenBase::TweenBase; + +protected: + uint32_t Current() override; +}; + +class TextColorTween : public ColorTween { +public: + using ColorTween::ColorTween; + + void Apply(View *view) override; +}; + +class VisibilityTween : public TweenBase { +public: + using TweenBase::TweenBase; + + void Apply(View *view) override; + +protected: + Visibility Current() override; +}; + +} // namespace From e32545b9c95b487ec667788fd038046061efc877 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Dec 2017 10:28:53 -0800 Subject: [PATCH 3/9] UI: Ensure tweens always complete. --- ext/native/ui/ui_tween.cpp | 23 +++++++++++++++-------- ext/native/ui/ui_tween.h | 33 ++++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/ext/native/ui/ui_tween.cpp b/ext/native/ui/ui_tween.cpp index 49be6ba10d..ea132505f4 100644 --- a/ext/native/ui/ui_tween.cpp +++ b/ext/native/ui/ui_tween.cpp @@ -4,23 +4,30 @@ namespace UI { -uint32_t ColorTween::Current() { - return colorBlend(to_, from_, Position()); +void Tween::Apply(View *view) { + if (time_now() >= start_ + duration_) + finishApplied_ = true; + + float pos = Position(); + DoApply(view, pos); } -void TextColorTween::Apply(View *view) { +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()); + tv->SetTextColor(Current(pos)); } -void VisibilityTween::Apply(View *view) { - view->SetVisibility(Current()); +void VisibilityTween::DoApply(View *view, float pos) { + view->SetVisibility(Current(pos)); } -Visibility VisibilityTween::Current() { +Visibility VisibilityTween::Current(float p) { // Prefer V_VISIBLE over V_GONE/V_INVISIBLE. - float p = Position(); if (from_ == V_VISIBLE && p < 1.0f) return from_; if (to_ == V_VISIBLE && p > 0.0f) diff --git a/ext/native/ui/ui_tween.h b/ext/native/ui/ui_tween.h index 70d1ad852c..41f061f262 100644 --- a/ext/native/ui/ui_tween.h +++ b/ext/native/ui/ui_tween.h @@ -17,10 +17,10 @@ public: } // Actually apply the tween to a view. - virtual void Apply(View *view) = 0; + void Apply(View *view); bool Finished() { - return time_now() >= start_ + duration_; + return finishApplied_ && time_now() >= start_ + duration_; } protected: @@ -32,8 +32,11 @@ protected: 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); }; @@ -47,8 +50,8 @@ public: // Use this to change the destination value. // Useful when a state flips while the tween is half-way through. - void Divert(const Value &newTo) { - const Value newFrom = Current(); + void Divert(const Value &newTo, float newDuration = -1.0f) { + const Value newFrom = Current(Position()); // Are we already part way through another transition? if (!Finished()) { @@ -57,6 +60,9 @@ public: } 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. @@ -65,15 +71,19 @@ public: } 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()); + Reset(Current(Position())); } // Use when the value is explicitly reset. Implicitly stops the tween. @@ -83,7 +93,7 @@ public: } protected: - virtual Value Current() = 0; + virtual Value Current(float pos) = 0; Value from_; Value to_; @@ -95,24 +105,25 @@ public: using TweenBase::TweenBase; protected: - uint32_t Current() override; + uint32_t Current(float pos) override; }; class TextColorTween : public ColorTween { public: using ColorTween::ColorTween; - void Apply(View *view) override; +protected: + void DoApply(View *view, float pos) override; }; class VisibilityTween : public TweenBase { public: using TweenBase::TweenBase; - void Apply(View *view) override; - protected: - Visibility Current() override; + void DoApply(View *view, float pos) override; + + Visibility Current(float pos) override; }; } // namespace From 15152fc55b371ad531e3f5cd699f0f906b01013e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Dec 2017 10:39:25 -0800 Subject: [PATCH 4/9] UI: Reorganize EmuScreen so it renders on loading. --- UI/EmuScreen.cpp | 90 +++++++++++++++++++++++++++--------------------- UI/EmuScreen.h | 1 + 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 76673b3f1f..f3272a27c5 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -794,11 +794,14 @@ void EmuScreen::processAxis(const AxisInput &axis, int direction) { void EmuScreen::CreateViews() { using namespace UI; + + 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); @@ -1026,6 +1029,7 @@ void EmuScreen::render() { // In this case, we need to double check it here. checkPowerDown(); thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR }); + renderUI(); return; } @@ -1075,44 +1079,7 @@ void EmuScreen::render() { const bool hasVisibleUI = !osm.IsEmpty() || saveStatePreview_->GetVisibility() != UI::V_GONE || g_Config.bShowTouchControls; 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 +1100,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) diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h index 8fefb5226c..119e2dba43 100644 --- a/UI/EmuScreen.h +++ b/UI/EmuScreen.h @@ -56,6 +56,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); From f23c7cf93b9395fb8d599f0778435e98b4bd608c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Dec 2017 10:40:09 -0800 Subject: [PATCH 5/9] UI: Apply tweens on Update(). --- ext/native/ui/view.cpp | 15 +++++++++++++++ ext/native/ui/view.h | 16 +++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/ext/native/ui/view.cpp b/ext/native/ui/view.cpp index ad977e12da..60a41f71de 100644 --- a/ext/native/ui/view.cpp +++ b/ext/native/ui/view.cpp @@ -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_++; } diff --git a/ext/native/ui/view.h b/ext/native/ui/view.h index a5a0cbac4c..74945f4727 100644 --- a/ext/native/ui/view.h +++ b/ext/native/ui/view.h @@ -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 &list); @@ -424,6 +423,12 @@ public: Point GetFocusPosition(FocusDirection dir); + template + T *AddTween(T *t) { + tweens_.push_back(t); + return t; + } + protected: // Inputs to layout std::unique_ptr layoutParams_; @@ -438,6 +443,8 @@ protected: // Outputs of layout. X/Y are absolute screen coordinates, hierarchy is "gone" here. Bounds bounds_; + std::vector 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 {} }; From 60c4ac58f0b475d1dfe359dd6047f1f6fcff2b85 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Dec 2017 10:54:38 -0800 Subject: [PATCH 6/9] System: Allow GPU to have delayed loading. --- Core/System.cpp | 8 ++++---- GPU/GPU.cpp | 6 ++++++ GPU/GPU.h | 1 + GPU/GPUCommon.h | 3 +++ GPU/GPUInterface.h | 1 + 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Core/System.cpp b/Core/System.cpp index 1030d914ee..aea38360ea 100644 --- a/Core/System.cpp +++ b/Core/System.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) { diff --git a/GPU/GPU.cpp b/GPU/GPU.cpp index 72f6bf1f4a..847e25661f 100644 --- a/GPU/GPU.cpp +++ b/GPU/GPU.cpp @@ -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)); diff --git a/GPU/GPU.h b/GPU/GPU.h index 2042fab015..b10bf19d9a 100644 --- a/GPU/GPU.h +++ b/GPU/GPU.h @@ -105,4 +105,5 @@ namespace Draw { } bool GPU_Init(GraphicsContext *ctx, Draw::DrawContext *thin3d); +bool GPU_IsReady(); void GPU_Shutdown(); diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 784293578c..83653c3673 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -70,6 +70,9 @@ public: Draw::DrawContext *GetDrawContext() override { return draw_; } + bool IsReady() override { + return true; + } void Reinitialize() override; void BeginHostFrame() override; diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index af5c1a36df..eeaa7c87a6 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -168,6 +168,7 @@ public: virtual Draw::DrawContext *GetDrawContext() = 0; // Initialization + virtual bool IsReady() = 0; virtual void InitClear() = 0; virtual void Reinitialize() = 0; From 9f000ddf35cf9c82960f1db35d589e892c122ddd Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Dec 2017 11:30:00 -0800 Subject: [PATCH 7/9] GLES: Avoid hanging while precompiling shaders. We want to continue updating the screen so it doesn't seem frozen. --- GPU/GLES/GPU_GLES.cpp | 7 +- GPU/GLES/GPU_GLES.h | 2 + GPU/GLES/ShaderManagerGLES.cpp | 133 +++++++++++++++++++++++---------- GPU/GLES/ShaderManagerGLES.h | 26 ++++++- 4 files changed, 125 insertions(+), 43 deletions(-) diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index eae91ae8d1..f159844697 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -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); diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index e3ffb81d4b..207cbcf29c 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -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; diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 46146f72ec..b83f49fb68 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -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) { diff --git a/GPU/GLES/ShaderManagerGLES.h b/GPU/GLES/ShaderManagerGLES.h index 0a5706f8c3..fe9c0226da 100644 --- a/GPU/GLES/ShaderManagerGLES.h +++ b/GPU/GLES/ShaderManagerGLES.h @@ -167,7 +167,8 @@ public: std::vector 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 vert; + std::vector frag; + std::vector> 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_; }; From c29ae5ff56a2608906b31b419cf3da95e7d82f18 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Dec 2017 11:49:34 -0800 Subject: [PATCH 8/9] fixup! UI: Ensure tweens always complete. --- ext/native/ui/ui_tween.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ext/native/ui/ui_tween.h b/ext/native/ui/ui_tween.h index 41f061f262..04ba166890 100644 --- a/ext/native/ui/ui_tween.h +++ b/ext/native/ui/ui_tween.h @@ -54,7 +54,7 @@ public: const Value newFrom = Current(Position()); // Are we already part way through another transition? - if (!Finished()) { + if (time_now() < start_ + duration_) { if (newTo == to_) { // Already on course. Don't change. } else if (newTo == from_) { @@ -92,6 +92,16 @@ public: 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; From fb91e7f043096da8a709119dc077705d0c0e123c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Dec 2017 11:56:42 -0800 Subject: [PATCH 9/9] UI: Show loading message while shaders preload. This opens the door to doing this for other things we do on game start, later. --- UI/EmuScreen.cpp | 25 +++++++++++++++++++++---- UI/EmuScreen.h | 5 +++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index f3272a27c5..4b0a29943d 100644 --- a/UI/EmuScreen.cpp +++ b/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); @@ -795,6 +804,7 @@ 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(); @@ -810,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) { @@ -823,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. @@ -1076,7 +1093,7 @@ 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) { renderUI(); diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h index 119e2dba43..17ddb9b9aa 100644 --- a/UI/EmuScreen.h +++ b/UI/EmuScreen.h @@ -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; @@ -98,4 +99,8 @@ private: double saveStatePreviewShownTime_; AsyncImageFileView *saveStatePreview_; int saveStateSlot_; + + UI::View *loadingView_ = nullptr; + UI::TextColorTween *loadingViewColor_ = nullptr; + UI::VisibilityTween *loadingViewVisible_ = nullptr; };