mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Merge pull request #21221 from hrydgard/renderpass-refactor-2
Refactor mainloop renderpass management
This commit is contained in:
38 files changed
+451
-415
No files matched your search
@@ -292,6 +292,11 @@ static VkAttachmentStoreOp ConvertStoreAction(VKRRenderPassStoreAction action) {
|
||||
|
||||
VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType, VkSampleCountFlagBits sampleCount) {
|
||||
bool isBackbuffer = rpType == RenderPassType::BACKBUFFER;
|
||||
if (isBackbuffer) {
|
||||
_dbg_assert_(key.colorLoadAction != VKRRenderPassLoadAction::KEEP);
|
||||
_dbg_assert_(key.depthLoadAction != VKRRenderPassLoadAction::KEEP);
|
||||
_dbg_assert_(key.stencilLoadAction != VKRRenderPassLoadAction::KEEP);
|
||||
}
|
||||
bool hasDepth = RenderPassTypeHasDepth(rpType);
|
||||
bool multiview = RenderPassTypeHasMultiView(rpType);
|
||||
bool multisample = RenderPassTypeHasMultisample(rpType);
|
||||
|
||||
@@ -982,20 +982,27 @@ void VulkanRenderManager::EndCurRenderStep() {
|
||||
curPipelineFlags_ = (PipelineFlags)0;
|
||||
}
|
||||
|
||||
void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassLoadAction color, VKRRenderPassLoadAction depth, VKRRenderPassLoadAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil, const char *tag) {
|
||||
void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassLoadAction colorLoad, VKRRenderPassLoadAction depthLoad, VKRRenderPassLoadAction stencilLoad, uint32_t clearColor, float clearDepth, uint8_t clearStencil, const char *tag) {
|
||||
_dbg_assert_(insideFrame_);
|
||||
|
||||
if (!fb) {
|
||||
// Backbuffer render passes have some requirements.
|
||||
_dbg_assert_(colorLoad != VKRRenderPassLoadAction::KEEP);
|
||||
_dbg_assert_(depthLoad != VKRRenderPassLoadAction::KEEP);
|
||||
_dbg_assert_(stencilLoad != VKRRenderPassLoadAction::KEEP);
|
||||
}
|
||||
|
||||
// Eliminate dupes (bind of the framebuffer we already are rendering to), instantly convert to a clear if possible.
|
||||
if (!steps_.empty() && steps_.back()->stepType == VKRStepType::RENDER && steps_.back()->render.framebuffer == fb) {
|
||||
u32 clearMask = 0;
|
||||
if (color == VKRRenderPassLoadAction::CLEAR) {
|
||||
if (colorLoad == VKRRenderPassLoadAction::CLEAR) {
|
||||
clearMask |= VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
}
|
||||
if (depth == VKRRenderPassLoadAction::CLEAR) {
|
||||
if (depthLoad == VKRRenderPassLoadAction::CLEAR) {
|
||||
clearMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
curPipelineFlags_ |= PipelineFlags::USES_DEPTH_STENCIL;
|
||||
}
|
||||
if (stencil == VKRRenderPassLoadAction::CLEAR) {
|
||||
if (stencilLoad == VKRRenderPassLoadAction::CLEAR) {
|
||||
clearMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
curPipelineFlags_ |= PipelineFlags::USES_DEPTH_STENCIL;
|
||||
}
|
||||
@@ -1057,25 +1064,25 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR
|
||||
// Older Mali drivers have issues with depth and stencil don't match load/clear/etc.
|
||||
// TODO: Determine which versions and do this only where necessary.
|
||||
u32 lateClearMask = 0;
|
||||
if (depth != stencil && vulkan_->GetPhysicalDeviceProperties().properties.vendorID == VULKAN_VENDOR_ARM) {
|
||||
if (stencil == VKRRenderPassLoadAction::DONT_CARE) {
|
||||
stencil = depth;
|
||||
} else if (depth == VKRRenderPassLoadAction::DONT_CARE) {
|
||||
depth = stencil;
|
||||
} else if (stencil == VKRRenderPassLoadAction::CLEAR) {
|
||||
depth = stencil;
|
||||
if (depthLoad != stencilLoad && vulkan_->GetPhysicalDeviceProperties().properties.vendorID == VULKAN_VENDOR_ARM) {
|
||||
if (stencilLoad == VKRRenderPassLoadAction::DONT_CARE) {
|
||||
stencilLoad = depthLoad;
|
||||
} else if (depthLoad == VKRRenderPassLoadAction::DONT_CARE) {
|
||||
depthLoad = stencilLoad;
|
||||
} else if (stencilLoad == VKRRenderPassLoadAction::CLEAR) {
|
||||
depthLoad = stencilLoad;
|
||||
lateClearMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
} else if (depth == VKRRenderPassLoadAction::CLEAR) {
|
||||
stencil = depth;
|
||||
} else if (depthLoad == VKRRenderPassLoadAction::CLEAR) {
|
||||
stencilLoad = depthLoad;
|
||||
lateClearMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
}
|
||||
}
|
||||
|
||||
VKRStep *step = new VKRStep{ VKRStepType::RENDER };
|
||||
step->render.framebuffer = fb;
|
||||
step->render.colorLoad = color;
|
||||
step->render.depthLoad = depth;
|
||||
step->render.stencilLoad = stencil;
|
||||
step->render.colorLoad = colorLoad;
|
||||
step->render.depthLoad = depthLoad;
|
||||
step->render.stencilLoad = stencilLoad;
|
||||
step->render.colorStore = VKRRenderPassStoreAction::STORE;
|
||||
step->render.depthStore = VKRRenderPassStoreAction::STORE;
|
||||
step->render.stencilStore = VKRRenderPassStoreAction::STORE;
|
||||
@@ -1092,7 +1099,7 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR
|
||||
|
||||
if (fb) {
|
||||
// If there's a KEEP, we naturally read from the framebuffer.
|
||||
if (color == VKRRenderPassLoadAction::KEEP || depth == VKRRenderPassLoadAction::KEEP || stencil == VKRRenderPassLoadAction::KEEP) {
|
||||
if (colorLoad == VKRRenderPassLoadAction::KEEP || depthLoad == VKRRenderPassLoadAction::KEEP || stencilLoad == VKRRenderPassLoadAction::KEEP) {
|
||||
step->dependencies.insert(fb);
|
||||
}
|
||||
}
|
||||
@@ -1118,7 +1125,7 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR
|
||||
}
|
||||
}
|
||||
|
||||
if (color == VKRRenderPassLoadAction::CLEAR || depth == VKRRenderPassLoadAction::CLEAR || stencil == VKRRenderPassLoadAction::CLEAR) {
|
||||
if (colorLoad == VKRRenderPassLoadAction::CLEAR || depthLoad == VKRRenderPassLoadAction::CLEAR || stencilLoad == VKRRenderPassLoadAction::CLEAR) {
|
||||
curRenderArea_.SetRect(0, 0, curWidth_, curHeight_);
|
||||
}
|
||||
|
||||
|
||||
@@ -1839,6 +1839,8 @@ DataFormat VKContext::PreferredFramebufferReadbackFormat(Framebuffer *src) {
|
||||
}
|
||||
|
||||
void VKContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) {
|
||||
_dbg_assert_(fbo != nullptr || equals(tag, "BackBuffer"))
|
||||
|
||||
VKFramebuffer *fb = (VKFramebuffer *)fbo;
|
||||
VKRRenderPassLoadAction color = (VKRRenderPassLoadAction)rp.color;
|
||||
VKRRenderPassLoadAction depth = (VKRRenderPassLoadAction)rp.depth;
|
||||
|
||||
@@ -129,6 +129,16 @@ void OnScreenDisplay::Show(OSDType type, std::string_view text, std::string_view
|
||||
entries_.insert(entries_.begin(), msg);
|
||||
}
|
||||
|
||||
void OnScreenDisplay::CancelById(std::string_view id) {
|
||||
for (auto iter = entries_.begin(); iter != entries_.end();) {
|
||||
if (iter->id == id) {
|
||||
iter = entries_.erase(iter);
|
||||
} else {
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnScreenDisplay::ShowOnOff(std::string_view message, bool on, float duration_s) {
|
||||
std::string msg(message);
|
||||
msg += ": ";
|
||||
|
||||
@@ -61,6 +61,7 @@ public:
|
||||
void Show(OSDType type, std::string_view text, std::string_view text2, std::string_view icon, float duration_s = 0.0f, const char *id = nullptr);
|
||||
|
||||
void ShowOnOff(std::string_view message, bool on, float duration_s = 0.0f);
|
||||
void CancelById(std::string_view id);
|
||||
|
||||
bool IsEmpty() const { return entries_.empty(); } // Shortcut to skip rendering.
|
||||
|
||||
|
||||
@@ -209,7 +209,30 @@ void ScreenManager::resized() {
|
||||
}
|
||||
|
||||
ScreenRenderFlags ScreenManager::render() {
|
||||
using namespace Draw;
|
||||
|
||||
ScreenRenderFlags flags = ScreenRenderFlags::NONE;
|
||||
|
||||
// First, go through the whole stack and have every screen render any non-backbuffer render passes.
|
||||
// In EmuScreen, this might result in running emulation.
|
||||
for (size_t i = 0; i < stack_.size(); i++) {
|
||||
const auto &layer = stack_[i];
|
||||
ScreenRenderMode mode = ScreenRenderMode::DEFAULT;
|
||||
if (i == stack_.size() - 1) {
|
||||
mode |= ScreenRenderMode::TOP;
|
||||
}
|
||||
flags |= layer.screen->PreRender(mode);
|
||||
}
|
||||
|
||||
// Now, start the final render pass. This is now the ONLY place where binding the null fb is allowed.
|
||||
draw_->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR}, "BackBuffer");
|
||||
getUIContext()->BeginFrame();
|
||||
|
||||
const Draw::Viewport viewport{0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f};
|
||||
draw_->SetViewport(viewport);
|
||||
draw_->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
|
||||
draw_->SetTargetSize(g_display.pixel_xres, g_display.pixel_yres);
|
||||
|
||||
if (!stack_.empty()) {
|
||||
// Collect the screens to render
|
||||
TinySet<Screen *, 6> layers;
|
||||
|
||||
+3
-2
@@ -72,11 +72,12 @@ ENUM_CLASS_BITOPS(ScreenRenderRole);
|
||||
|
||||
class Screen {
|
||||
public:
|
||||
Screen() : screenManager_(nullptr) { }
|
||||
Screen() = default;
|
||||
virtual ~Screen();
|
||||
|
||||
virtual void onFinish(DialogResult reason) {}
|
||||
virtual void update() {}
|
||||
virtual ScreenRenderFlags PreRender(ScreenRenderMode mode) { return ScreenRenderFlags::NONE; }
|
||||
virtual ScreenRenderFlags render(ScreenRenderMode mode) = 0;
|
||||
virtual void resized() {}
|
||||
virtual void dialogFinished(const Screen *dialog, DialogResult result) {}
|
||||
@@ -114,7 +115,7 @@ protected:
|
||||
}
|
||||
|
||||
private:
|
||||
ScreenManager *screenManager_;
|
||||
ScreenManager *screenManager_ = nullptr;
|
||||
int token_ = -1;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Screen);
|
||||
|
||||
+7
-30
@@ -147,13 +147,6 @@ void UIScreen::update() {
|
||||
|
||||
DoRecreateViews();
|
||||
|
||||
if (root_) {
|
||||
DialogResult result = UpdateViewHierarchy(root_);
|
||||
if (result != DR_NONE) {
|
||||
TriggerFinish(result);
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
QueuedEvent ev{};
|
||||
{
|
||||
@@ -188,6 +181,13 @@ void UIScreen::update() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (root_) {
|
||||
DialogResult result = UpdateViewHierarchy(root_);
|
||||
if (result != DR_NONE) {
|
||||
TriggerFinish(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UIScreen::deviceLost() {
|
||||
@@ -200,30 +200,7 @@ void UIScreen::deviceRestored(Draw::DrawContext *draw) {
|
||||
root_->DeviceRestored(draw);
|
||||
}
|
||||
|
||||
void UIScreen::SetupViewport() {
|
||||
using namespace Draw;
|
||||
Draw::DrawContext *draw = screenManager()->getDrawContext();
|
||||
_dbg_assert_(draw != nullptr);
|
||||
// Bind and clear the back buffer
|
||||
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }, "UI");
|
||||
screenManager()->getUIContext()->BeginFrame();
|
||||
|
||||
Draw::Viewport viewport;
|
||||
viewport.TopLeftX = 0;
|
||||
viewport.TopLeftY = 0;
|
||||
viewport.Width = g_display.pixel_xres;
|
||||
viewport.Height = g_display.pixel_yres;
|
||||
viewport.MaxDepth = 1.0;
|
||||
viewport.MinDepth = 0.0;
|
||||
draw->SetViewport(viewport);
|
||||
draw->SetTargetSize(g_display.pixel_xres, g_display.pixel_yres);
|
||||
}
|
||||
|
||||
ScreenRenderFlags UIScreen::render(ScreenRenderMode mode) {
|
||||
if (mode & ScreenRenderMode::FIRST) {
|
||||
SetupViewport();
|
||||
}
|
||||
|
||||
DoRecreateViews();
|
||||
|
||||
UIContext &uiContext = *screenManager()->getUIContext();
|
||||
|
||||
@@ -77,7 +77,6 @@ protected:
|
||||
virtual void DrawBackground(UIContext &ui) {}
|
||||
virtual void DrawForeground(UIContext &ui) {}
|
||||
|
||||
void SetupViewport();
|
||||
void DoRecreateViews();
|
||||
|
||||
bool recreateViews_ = true;
|
||||
|
||||
@@ -68,7 +68,6 @@ void Event::Add(std::function<void(EventParams&)> func) {
|
||||
func_ = func;
|
||||
}
|
||||
|
||||
// Call this from input thread or whatever, it doesn't matter
|
||||
void Event::Trigger(EventParams &e) {
|
||||
if (!func_) {
|
||||
return;
|
||||
@@ -76,7 +75,6 @@ void Event::Trigger(EventParams &e) {
|
||||
EventTriggered(this, e);
|
||||
}
|
||||
|
||||
// Call this from UI thread
|
||||
void Event::Dispatch(EventParams &e) {
|
||||
if (func_)
|
||||
func_(e);
|
||||
|
||||
+1
-1
@@ -237,7 +237,7 @@ typedef std::function<void(EventParams &)> EventCallback;
|
||||
|
||||
class Event {
|
||||
public:
|
||||
Event() {}
|
||||
Event() = default;
|
||||
~Event();
|
||||
// Call this from input thread or whatever, it doesn't matter
|
||||
void Trigger(EventParams &e);
|
||||
|
||||
@@ -321,17 +321,17 @@ float GetTargetScore(const Point2D &originPos, int originIndex, const View *orig
|
||||
distance = 0.001f;
|
||||
}
|
||||
float overlap = 0.0f;
|
||||
float dirX = dx / distance;
|
||||
float dirY = dy / distance;
|
||||
const float dirX = dx / distance;
|
||||
const float dirY = dy / distance;
|
||||
|
||||
bool wrongDirection = false;
|
||||
bool vertical = false;
|
||||
float horizOverlap = HorizontalOverlap(origin->GetBounds(), destination->GetBounds());
|
||||
float vertOverlap = VerticalOverlap(origin->GetBounds(), destination->GetBounds());
|
||||
const float horizOverlap = HorizontalOverlap(origin->GetBounds(), destination->GetBounds());
|
||||
const float vertOverlap = VerticalOverlap(origin->GetBounds(), destination->GetBounds());
|
||||
if (horizOverlap == 1.0f && vertOverlap == 1.0f) {
|
||||
if (direction != FOCUS_PREV_PAGE && direction != FOCUS_NEXT_PAGE) {
|
||||
INFO_LOG(Log::UI, "Contain overlap");
|
||||
return 0.0;
|
||||
INFO_LOG(Log::UI, "Contain overlap: %s, %s", origin->Tag().c_str(), destination->Tag().c_str());
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
float originSize = 0.0f;
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ struct DisplayLayoutConfig : public ConfigBlock {
|
||||
int iCardboardScreenSize = 50; // Screen Size (in %)
|
||||
int iCardboardXShift = 0; // X-Shift of Screen (in %)
|
||||
int iCardboardYShift = 0; // Y-Shift of Screen (in %)
|
||||
bool bImmersiveMode = true; // Mode on Android Kitkat 4.4 and later that hides the back button etc.
|
||||
bool bImmersiveMode = false; // Mode on Android Kitkat 4.4 and later that hides the back button etc.
|
||||
|
||||
bool InternalRotationIsPortrait() const;
|
||||
bool CanResetToDefault() const override { return true; }
|
||||
|
||||
@@ -662,7 +662,6 @@ void __DisplayFlip(int cyclesLate) {
|
||||
}
|
||||
if (nextFrame) {
|
||||
gpu->SetCurFramebufferDirty(fbReallyDirty);
|
||||
gpu->CopyDisplayToOutput(g_displayLayoutConfigCached);
|
||||
if (fbReallyDirty) {
|
||||
DisplayFireActualFlip();
|
||||
}
|
||||
|
||||
+69
-63
@@ -77,7 +77,6 @@ double g_lastSaveTime = -1.0;
|
||||
static bool needsProcess = false;
|
||||
static bool needsRestart = false;
|
||||
static std::mutex mutex;
|
||||
static int screenshotFailures = 0;
|
||||
static bool hasLoadedState = false;
|
||||
static const int STALE_STATE_USES = 2;
|
||||
// 4 hours of total gameplay since the virtual PSP started the game.
|
||||
@@ -102,21 +101,25 @@ enum class OperationType {
|
||||
Load,
|
||||
Verify,
|
||||
Rewind,
|
||||
SaveScreenshot,
|
||||
};
|
||||
|
||||
struct Operation {
|
||||
// The slot number is for visual purposes only. Set to -1 for operations where we don't display a message for example.
|
||||
Operation(OperationType t, const Path &f, int slot_, Callback cb)
|
||||
: type(t), filename(f), callback(cb), slot(slot_) {}
|
||||
struct Operation {
|
||||
// The slot number is for visual purposes only. Set to -1 for operations where we don't display a message for example.
|
||||
Operation(OperationType t, const Path &f, int slot_, Callback cb)
|
||||
: type(t), filename(f), callback(cb), slot(slot_) {}
|
||||
|
||||
OperationType type;
|
||||
Path filename;
|
||||
Callback callback;
|
||||
int slot;
|
||||
};
|
||||
OperationType type;
|
||||
Path filename;
|
||||
Callback callback;
|
||||
int slot;
|
||||
};
|
||||
|
||||
static std::vector<Operation> pending;
|
||||
static std::vector<Operation> g_pendingOperations;
|
||||
|
||||
// If this isn't empty, a screenshot operation is pending. It's protected by mutex.
|
||||
Path g_screenshotPath;
|
||||
|
||||
int g_screenshotFailures;
|
||||
|
||||
CChunkFileReader::Error SaveToRam(std::vector<u8> &data) {
|
||||
SaveStart state;
|
||||
@@ -202,7 +205,7 @@ enum class OperationType {
|
||||
return;
|
||||
}
|
||||
if (Achievements::HardcoreModeActive()) {
|
||||
if (g_Config.bAchievementsSaveStateInHardcoreMode && ((op.type == SaveState::OperationType::Save) || (op.type == OperationType::SaveScreenshot))) {
|
||||
if (g_Config.bAchievementsSaveStateInHardcoreMode && ((op.type == SaveState::OperationType::Save))) {
|
||||
// We allow saving in hardcore mode if this setting is on.
|
||||
} else {
|
||||
// Operation not allowed
|
||||
@@ -211,7 +214,7 @@ enum class OperationType {
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> guard(mutex);
|
||||
pending.push_back(op);
|
||||
g_pendingOperations.push_back(op);
|
||||
|
||||
// Don't actually run it until next frame.
|
||||
// It's possible there might be a duplicate but it won't hurt us.
|
||||
@@ -253,11 +256,6 @@ enum class OperationType {
|
||||
Enqueue(Operation(OperationType::Rewind, Path(), -1, callback));
|
||||
}
|
||||
|
||||
static void SaveScreenshot(const Path &filename) {
|
||||
screenshotFailures = 0;
|
||||
Enqueue(Operation(OperationType::SaveScreenshot, filename, -1, nullptr));
|
||||
}
|
||||
|
||||
bool CanRewind() {
|
||||
return !rewindStates.Empty();
|
||||
}
|
||||
@@ -383,6 +381,12 @@ enum class OperationType {
|
||||
}
|
||||
}
|
||||
|
||||
static void ScheduleSaveScreenshot(const Path &path) {
|
||||
std::lock_guard<std::mutex> guard(mutex);
|
||||
g_screenshotPath = path;
|
||||
g_screenshotFailures = 0;
|
||||
}
|
||||
|
||||
void LoadSlot(std::string_view gamePrefix, int slot, Callback callback) {
|
||||
if (!NetworkAllowSaveState()) {
|
||||
return;
|
||||
@@ -481,7 +485,7 @@ enum class OperationType {
|
||||
DeleteIfExists(shotUndo);
|
||||
RenameIfExists(shot, shotUndo);
|
||||
}
|
||||
SaveScreenshot(shot);
|
||||
ScheduleSaveScreenshot(shot);
|
||||
Save(fn.WithExtraExtension(".tmp"), slot, renameCallback);
|
||||
} else {
|
||||
if (callback) {
|
||||
@@ -686,8 +690,8 @@ enum class OperationType {
|
||||
|
||||
std::vector<Operation> Flush() {
|
||||
std::lock_guard<std::mutex> guard(mutex);
|
||||
std::vector<Operation> copy = pending;
|
||||
pending.clear();
|
||||
std::vector<Operation> copy = g_pendingOperations;
|
||||
g_pendingOperations.clear();
|
||||
|
||||
return copy;
|
||||
}
|
||||
@@ -772,23 +776,21 @@ enum class OperationType {
|
||||
// NOTE: This can cause ending of the current renderpass, due to the readback needed for the screenshot.
|
||||
// TODO: This should run the actual operations on a thread. While this returns true (for example), emulation
|
||||
// *must* not run further, in order not to disturb the current state operation.
|
||||
bool Process() {
|
||||
void Process() {
|
||||
rewindStates.Process();
|
||||
|
||||
if (!needsProcess)
|
||||
return false;
|
||||
return;
|
||||
needsProcess = false;
|
||||
|
||||
if (!__KernelIsRunning()) {
|
||||
ERROR_LOG(Log::SaveState, "Savestate failure: Unable to load without kernel, this should never happen.");
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Operation> operations = Flush();
|
||||
SaveStart state;
|
||||
|
||||
bool readbackImage = false;
|
||||
|
||||
for (const auto &op : operations) {
|
||||
CChunkFileReader::Error result;
|
||||
Status callbackResult;
|
||||
@@ -913,40 +915,6 @@ enum class OperationType {
|
||||
}
|
||||
break;
|
||||
|
||||
case OperationType::SaveScreenshot:
|
||||
{
|
||||
_dbg_assert_(!op.callback);
|
||||
|
||||
int maxResMultiplier = 2;
|
||||
ScreenshotResult tempResult = TakeGameScreenshot(nullptr, op.filename, ScreenshotFormat::JPG, SCREENSHOT_DISPLAY, maxResMultiplier, [](bool success) {
|
||||
if (success) {
|
||||
screenshotFailures = 0;
|
||||
}
|
||||
});
|
||||
|
||||
switch (tempResult) {
|
||||
case ScreenshotResult::ScreenshotNotPossible:
|
||||
// Try again soon, for a short while.
|
||||
callbackResult = Status::FAILURE;
|
||||
WARN_LOG(Log::SaveState, "Failed to take a screenshot for the savestate! (%s) The savestate will lack an icon.", op.filename.c_str());
|
||||
if (coreState != CORE_STEPPING_CPU && screenshotFailures++ < SCREENSHOT_FAILURE_RETRIES) {
|
||||
// Requeue for next frame (if we were stepping, no point, will just spam errors quickly).
|
||||
SaveScreenshot(op.filename);
|
||||
}
|
||||
break;
|
||||
case ScreenshotResult::DelayedResult:
|
||||
case ScreenshotResult::Success:
|
||||
// We might not know if the file write succeeded yet though.
|
||||
callbackResult = Status::SUCCESS;
|
||||
readbackImage = true;
|
||||
break;
|
||||
case ScreenshotResult::FailedToWriteFile:
|
||||
// Can't reach here when we pass in a callback to TakeGameScreenshot.
|
||||
callbackResult = Status::SUCCESS;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ERROR_LOG(Log::SaveState, "Savestate failure: unknown operation type %d", (int)op.type);
|
||||
callbackResult = Status::FAILURE;
|
||||
@@ -961,8 +929,6 @@ enum class OperationType {
|
||||
// Avoid triggering frame skipping due to slowdown
|
||||
__DisplaySetWasPaused();
|
||||
}
|
||||
|
||||
return readbackImage;
|
||||
}
|
||||
|
||||
void NotifySaveData() {
|
||||
@@ -1006,4 +972,44 @@ enum class OperationType {
|
||||
return time_now_d() - g_lastSaveTime;
|
||||
}
|
||||
}
|
||||
|
||||
bool ProcessScreenshot(bool skipBufferEffects) {
|
||||
Path screenshotPath;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(mutex);
|
||||
if (!g_screenshotPath.empty()) {
|
||||
screenshotPath = g_screenshotPath;
|
||||
g_screenshotPath.clear();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Savestate thumbnails don't need to be bigger.
|
||||
constexpr int maxResMultiplier = 2;
|
||||
ScreenshotResult tempResult = TakeGameScreenshot(nullptr, screenshotPath, ScreenshotFormat::JPG, SCREENSHOT_DISPLAY, maxResMultiplier, [](bool success) {
|
||||
if (success) {
|
||||
g_screenshotFailures = 0;
|
||||
}
|
||||
});
|
||||
|
||||
switch (tempResult) {
|
||||
case ScreenshotResult::ScreenshotNotPossible:
|
||||
// Try again soon, for a short while.
|
||||
WARN_LOG(Log::SaveState, "Failed to take a screenshot for the savestate! (%s) The savestate will lack an icon.", g_screenshotPath.c_str());
|
||||
if (coreState != CORE_STEPPING_CPU && g_screenshotFailures++ < SCREENSHOT_FAILURE_RETRIES) {
|
||||
// Requeue for next frame (if we were stepping, no point, will just spam errors quickly).
|
||||
ScheduleSaveScreenshot(g_screenshotPath);
|
||||
}
|
||||
break;
|
||||
case ScreenshotResult::FailedToWriteFile:
|
||||
break;
|
||||
case ScreenshotResult::DelayedResult:
|
||||
return true;
|
||||
case ScreenshotResult::Success:
|
||||
return true;
|
||||
}
|
||||
return false; // Didn't take a screenshot right now.
|
||||
}
|
||||
|
||||
} // namespace SaveState
|
||||
+5
-1
@@ -110,7 +110,11 @@ namespace SaveState {
|
||||
bool IsOldVersion();
|
||||
|
||||
// Check if there's any save stating needing to be done. Normally called once per frame.
|
||||
bool Process();
|
||||
void Process();
|
||||
|
||||
// Separate function to just process screenshots, as they need to be done at a specific time in a frame.
|
||||
// Returns true if a screenshot was taken.
|
||||
bool ProcessScreenshot(bool skipBufferEffects);
|
||||
|
||||
// Notify save state code that new save data has been written.
|
||||
void NotifySaveData();
|
||||
|
||||
+40
-5
@@ -325,8 +325,13 @@ ScreenshotResult TakeGameScreenshot(Draw::DrawContext *draw, const Path &filenam
|
||||
if (!gpuDebug->GetCurrentFramebuffer(buf, type == SCREENSHOT_RENDER ? GPU_DBG_FRAMEBUF_RENDER : GPU_DBG_FRAMEBUF_DISPLAY, maxRes)) {
|
||||
return ScreenshotResult::ScreenshotNotPossible;
|
||||
}
|
||||
w = maxRes > 0 ? 480 * maxRes : PSP_CoreParameter().renderWidth;
|
||||
h = maxRes > 0 ? 272 * maxRes : PSP_CoreParameter().renderHeight;
|
||||
if (buf.IsBackBuffer()) {
|
||||
w = buf.GetStride();
|
||||
h = buf.GetHeight();
|
||||
} else {
|
||||
w = maxRes > 0 ? 480 * maxRes : buf.GetStride();
|
||||
h = maxRes > 0 ? 272 * maxRes : buf.GetHeight();
|
||||
}
|
||||
} else if (g_display.rotation != DisplayRotation::ROTATE_0) {
|
||||
_dbg_assert_(draw);
|
||||
GPUDebugBuffer temp;
|
||||
@@ -343,12 +348,42 @@ ScreenshotResult TakeGameScreenshot(Draw::DrawContext *draw, const Path &filenam
|
||||
|
||||
if (callback) {
|
||||
g_threadManager.EnqueueTask(new IndependentTask(TaskType::IO_BLOCKING, TaskPriority::LOW,
|
||||
[buf = std::move(buf), callback = std::move(callback), filename, fmt, w, h]() {
|
||||
[buf = std::move(buf), callback = std::move(callback), filename, fmt, w, h, maxRes]() {
|
||||
u8 *flipbuffer = nullptr;
|
||||
u32 width = w, height = h;
|
||||
const u8 *buffer = ConvertBufferToScreenshot(buf, false, flipbuffer, width, height);
|
||||
bool success = Save888RGBScreenshot(filename, fmt, buffer, width, height);
|
||||
delete[] flipbuffer;
|
||||
|
||||
bool success;
|
||||
if (width <= 480 * maxRes) {
|
||||
success = Save888RGBScreenshot(filename, fmt, buffer, width, height);
|
||||
delete[] flipbuffer;
|
||||
} else {
|
||||
u8 *shrinkBuffer = new u8[width * height * 3];
|
||||
memcpy(shrinkBuffer, buffer, width * height * 3);
|
||||
delete[] flipbuffer;
|
||||
|
||||
// TODO: Speed this thing up.
|
||||
while (width > 480 * maxRes) {
|
||||
u8 *halfSize = new u8[(width / 2) * (height / 2) * 3];
|
||||
for (int y = 0; y < height / 2; y++) {
|
||||
for (int x = 0; x < width / 2; x++) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
halfSize[(y * (width / 2) + x) * 3 + c] =
|
||||
(shrinkBuffer[((y * 2) * width + (x * 2)) * 3 + c] +
|
||||
shrinkBuffer[((y * 2) * width + (x * 2 + 1)) * 3 + c] +
|
||||
shrinkBuffer[(((y * 2) + 1) * width + (x * 2)) * 3 + c] +
|
||||
shrinkBuffer[(((y * 2) + 1) * width + (x * 2 + 1)) * 3 + c]) / 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::swap(shrinkBuffer, halfSize);
|
||||
delete[] halfSize;
|
||||
width /= 2;
|
||||
height /= 2;
|
||||
}
|
||||
success = Save888RGBScreenshot(filename, fmt, shrinkBuffer, width, height);
|
||||
}
|
||||
|
||||
System_RunOnMainThread([success, callback = std::move(callback)]() {
|
||||
callback(success);
|
||||
});
|
||||
|
||||
+9
-3
@@ -252,17 +252,23 @@ static void ShowCompatWarnings(const Compatibility &compat) {
|
||||
// UI changes are best done after PSP_InitStart.
|
||||
if (compat.flags().RequireBufferedRendering && g_Config.bSkipBufferEffects && !g_Config.bSoftwareRendering) {
|
||||
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
|
||||
g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("BufferedRenderingRequired", "Warning: This game requires Rendering Mode to be set to Buffered."), 10.0f);
|
||||
g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("BufferedRenderingRequired", "Warning: This game requires Rendering Mode to be set to Buffered."), 10.0f, "bufreq");
|
||||
} else {
|
||||
g_OSD.CancelById("bufreq");
|
||||
}
|
||||
|
||||
if (compat.flags().RequireBlockTransfer && g_Config.iSkipGPUReadbackMode != (int)SkipGPUReadbackMode::NO_SKIP && !PSP_CoreParameter().compat.flags().ForceEnableGPUReadback) {
|
||||
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
|
||||
g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("BlockTransferRequired", "Warning: This game requires Skip GPU Readbacks be set to No."), 10.0f);
|
||||
g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("BlockTransferRequired", "Warning: This game requires Skip GPU Readbacks be set to No."), 10.0f, "blockxfer");
|
||||
} else {
|
||||
g_OSD.CancelById("blockxfer");
|
||||
}
|
||||
|
||||
if (compat.flags().RequireDefaultCPUClock && g_Config.iLockedCPUSpeed != 0) {
|
||||
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
|
||||
g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("DefaultCPUClockRequired", "Warning: This game requires the CPU clock to be set to default."), 10.0f);
|
||||
g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("DefaultCPUClockRequired", "Warning: This game requires the CPU clock to be set to default."), 10.0f, "defaultclock");
|
||||
} else {
|
||||
g_OSD.CancelById("defaultclock");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1559,9 +1559,11 @@ bool FramebufferManagerCommon::DrawFramebufferToOutput(const DisplayLayoutConfig
|
||||
constexpr float u0 = 0.0f, u1 = 480.0f / 512.0f;
|
||||
constexpr float v0 = 0.0f, v1 = 1.0f;
|
||||
|
||||
presentation_->UpdateUniforms(textureCache_->VideoIsPlaying());
|
||||
presentation_->SourceTexture(pixelsTex, 512, 272);
|
||||
presentation_->RunPostshaderPasses(config, flags, uvRotation, u0, v0, u1, v1);
|
||||
if (useBufferedRendering_) {
|
||||
presentation_->UpdateUniforms(textureCache_->VideoIsPlaying());
|
||||
presentation_->SourceTexture(pixelsTex, 512, 272);
|
||||
presentation_->RunPostshaderPasses(config, flags, uvRotation, u0, v0, u1, v1);
|
||||
}
|
||||
|
||||
// PresentationCommon sets all kinds of state, we can't rely on anything.
|
||||
gstate_c.Dirty(DIRTY_ALL);
|
||||
@@ -1576,7 +1578,19 @@ void FramebufferManagerCommon::SetViewport2D(int x, int y, int w, int h) {
|
||||
draw_->SetViewport(viewport);
|
||||
}
|
||||
|
||||
void FramebufferManagerCommon::CopyDisplayToOutput(const DisplayLayoutConfig &config, bool reallyDirty) {
|
||||
void FramebufferManagerCommon::CopyDisplayToOutput(const DisplayLayoutConfig &config) {
|
||||
// PresentationCommon sets all kinds of state, we can't rely on anything.
|
||||
gstate_c.Dirty(DIRTY_ALL);
|
||||
DiscardFramebufferCopy();
|
||||
currentRenderVfb_ = nullptr;
|
||||
if (useBufferedRendering_) {
|
||||
presentation_->CopyToOutput(config);
|
||||
} else {
|
||||
presentation_->NotifyPresent();
|
||||
}
|
||||
}
|
||||
|
||||
void FramebufferManagerCommon::PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config, bool reallyDirty) {
|
||||
DownloadFramebufferOnSwitch(currentRenderVfb_);
|
||||
shaderManager_->DirtyLastShader();
|
||||
|
||||
@@ -1588,11 +1602,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput(const DisplayLayoutConfig &co
|
||||
DEBUG_LOG(Log::FrameBuf, "Display disabled, displaying only black");
|
||||
}
|
||||
// No framebuffer to display! Clear to black.
|
||||
if (useBufferedRendering_) {
|
||||
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }, "CopyDisplayToOutput");
|
||||
}
|
||||
gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE);
|
||||
presentation_->NotifyPresent();
|
||||
presentation_->SourceBlank();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1651,27 +1661,19 @@ void FramebufferManagerCommon::CopyDisplayToOutput(const DisplayLayoutConfig &co
|
||||
if (Memory::IsValidAddress(fbaddr)) {
|
||||
// The game is displaying something directly from RAM. In GTA, it's decoded video.
|
||||
// If successful, this effectively calls presentation_->NotifyPresent();
|
||||
if (DrawFramebufferToOutput(config, Memory::GetPointerUnchecked(fbaddr), displayStride_, displayFormat_)) {
|
||||
presentation_->CopyToOutput(config);
|
||||
} else {
|
||||
if (useBufferedRendering_) {
|
||||
// Bind and clear the backbuffer. This should be the first time during the frame that it's bound.
|
||||
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }, "CopyDisplayToOutput_DrawError");
|
||||
}
|
||||
presentation_->NotifyPresent();
|
||||
if (!DrawFramebufferToOutput(config, Memory::GetPointerUnchecked(fbaddr), displayStride_, displayFormat_)) {
|
||||
// No framebuffer to display! Clear to black.
|
||||
presentation_->SourceBlank();
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
DEBUG_LOG(Log::FrameBuf, "Found no FBO to display! displayFBPtr = %08x", fbaddr);
|
||||
// No framebuffer to display! Clear to black.
|
||||
if (useBufferedRendering_) {
|
||||
// Bind and clear the backbuffer. This should be the first time during the frame that it's bound.
|
||||
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }, "CopyDisplayToOutput_NoFBO");
|
||||
} // For non-buffered rendering, every frame is cleared anyway.
|
||||
// TODO: Draw a black rectangle, will be important once we add backgrounds.
|
||||
gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE);
|
||||
presentation_->NotifyPresent();
|
||||
return;
|
||||
// No framebuffer to display! Clear to black.
|
||||
presentation_->SourceBlank();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
vfb->usageFlags |= FB_USAGE_DISPLAYED_FRAMEBUFFER;
|
||||
@@ -1688,6 +1690,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput(const DisplayLayoutConfig &co
|
||||
displayFramebuf_ = vfb;
|
||||
|
||||
if (vfb->fbo) {
|
||||
_dbg_assert_(useBufferedRendering_);
|
||||
if (GetUIState() != UISTATE_PAUSEMENU) {
|
||||
if (Core_IsStepping())
|
||||
VERBOSE_LOG(Log::FrameBuf, "Displaying FBO %08x", vfb->fb_address);
|
||||
@@ -1732,20 +1735,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput(const DisplayLayoutConfig &co
|
||||
presentation_->UpdateUniforms(textureCache_->VideoIsPlaying());
|
||||
presentation_->SourceFramebuffer(vfb->fbo, actualWidth, actualHeight);
|
||||
presentation_->RunPostshaderPasses(config, flags, uvRotation, u0, v0, u1, v1);
|
||||
presentation_->CopyToOutput(config);
|
||||
} else if (useBufferedRendering_) {
|
||||
WARN_LOG(Log::FrameBuf, "Using buffered rendering, and current VFB lacks an FBO: %08x", vfb->fb_address);
|
||||
} else {
|
||||
// This is OK because here we're in "skip buffered" mode, so even if we haven't presented
|
||||
// we will have a render target.
|
||||
presentation_->NotifyPresent();
|
||||
}
|
||||
|
||||
// This may get called mid-draw if the game uses an immediate flip.
|
||||
// PresentationCommon sets all kinds of state, we can't rely on anything.
|
||||
gstate_c.Dirty(DIRTY_ALL);
|
||||
DiscardFramebufferCopy();
|
||||
currentRenderVfb_ = nullptr;
|
||||
}
|
||||
|
||||
void FramebufferManagerCommon::DecimateFBOs() {
|
||||
@@ -3035,9 +3025,10 @@ bool FramebufferManagerCommon::GetFramebuffer(u32 fb_address, int fb_stride, GEB
|
||||
}
|
||||
|
||||
if (!useBufferedRendering_) {
|
||||
// Safety check.
|
||||
w = std::min(w, PSP_CoreParameter().pixelWidth);
|
||||
h = std::min(h, PSP_CoreParameter().pixelHeight);
|
||||
// In this mode, we can only screenshot the backbuffer, and we can't resize with a simple blit (well technically we could, but complicated)
|
||||
w = PSP_CoreParameter().pixelWidth;
|
||||
h = PSP_CoreParameter().pixelHeight;
|
||||
buffer.SetIsBackbuffer(true);
|
||||
}
|
||||
|
||||
// TODO: Maybe should handle flipY inside CopyFramebufferToMemorySync somehow?
|
||||
@@ -3448,10 +3439,6 @@ void FramebufferManagerCommon::DrawActiveTexture(float x, float y, float w, floa
|
||||
void FramebufferManagerCommon::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp, RasterChannel channel, const char *tag) {
|
||||
if (!dst->fbo || !src->fbo || !useBufferedRendering_) {
|
||||
// This can happen if they recently switched from non-buffered.
|
||||
if (useBufferedRendering_) {
|
||||
// Just bind the back buffer for rendering, forget about doing anything else as we're in a weird state.
|
||||
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, "BlitFramebuffer");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -334,7 +334,8 @@ public:
|
||||
void RebindFramebuffer(const char *tag);
|
||||
std::vector<const VirtualFramebuffer *> GetFramebufferList() const;
|
||||
|
||||
void CopyDisplayToOutput(const DisplayLayoutConfig &config, bool reallyDirty);
|
||||
void PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config, bool reallyDirty);
|
||||
void CopyDisplayToOutput(const DisplayLayoutConfig &config);
|
||||
|
||||
bool NotifyFramebufferCopy(u32 src, u32 dest, int size, GPUCopyFlag flags, u32 skipDrawReason);
|
||||
void PerformWriteFormattedFromMemory(u32 addr, int size, int width, GEBufferFormat fmt);
|
||||
|
||||
@@ -182,6 +182,9 @@ struct GPUDebugBuffer {
|
||||
|
||||
u32 PixelSize() const;
|
||||
|
||||
void SetIsBackbuffer(bool isBackBuffer) { isBackBuffer_ = isBackBuffer; }
|
||||
bool IsBackBuffer() const { return isBackBuffer_; }
|
||||
|
||||
private:
|
||||
bool alloc_ = false;
|
||||
u8 *data_ = nullptr;
|
||||
@@ -189,6 +192,7 @@ private:
|
||||
u32 height_ = 0;
|
||||
GPUDebugBufferFormat fmt_ = GPU_DBG_FORMAT_INVALID;
|
||||
bool flipped_ = false;
|
||||
bool isBackBuffer_ = false;
|
||||
};
|
||||
|
||||
struct GPUDebugVertex {
|
||||
|
||||
@@ -599,6 +599,15 @@ Draw::ShaderModule *PresentationCommon::CompileShaderModule(ShaderStage stage, S
|
||||
return draw_->CreateShaderModule(stage, lang_, (const uint8_t *)translated.c_str(), translated.size(), "postshader");
|
||||
}
|
||||
|
||||
void PresentationCommon::SourceBlank() {
|
||||
DoRelease(srcTexture_);
|
||||
DoRelease(srcFramebuffer_);
|
||||
|
||||
srcWidth_ = 0;
|
||||
srcHeight_ = 0;
|
||||
}
|
||||
|
||||
// If texture == null, that means there's nothing to display, so we should show a black screen in CopyToOutput.
|
||||
void PresentationCommon::SourceTexture(Draw::Texture *texture, int bufferWidth, int bufferHeight) {
|
||||
// AddRef before release and assign in case it's the same.
|
||||
texture->AddRef();
|
||||
@@ -682,6 +691,11 @@ void PresentationCommon::RunPostshaderPasses(const DisplayLayoutConfig &config,
|
||||
}
|
||||
CalculateDisplayOutputRect(config, &rc_, 480.0f, 272.0f, frame, uvRotation);
|
||||
|
||||
if (!srcTexture_ && !srcFramebuffer_) {
|
||||
// Presenting blank, no need to run post shaders. But we did compute the output rect.
|
||||
return;
|
||||
}
|
||||
|
||||
// To make buffer updates easier, we use one array of verts.
|
||||
int postVertsOffset = (int)sizeof(Vertex) * 4;
|
||||
|
||||
@@ -884,9 +898,14 @@ void PresentationCommon::CopyToOutput(const DisplayLayoutConfig &config) {
|
||||
int lastWidth = srcWidth_;
|
||||
int lastHeight = srcHeight_;
|
||||
|
||||
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "FinalBlit");
|
||||
draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_);
|
||||
|
||||
if (!srcFramebuffer_ && !srcTexture_) {
|
||||
// Bound blank. We're done (although we could draw a black rectangle here).
|
||||
presentedThisFrame_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Draw::Pipeline *pipeline = (outputFlags_ & OutputFlags::RB_SWIZZLE) ? texColorRBSwizzle_ : texColor_;
|
||||
|
||||
if (useStereo) {
|
||||
|
||||
@@ -124,6 +124,9 @@ public:
|
||||
void DeviceRestore(Draw::DrawContext *draw);
|
||||
|
||||
void UpdateUniforms(bool hasVideo);
|
||||
|
||||
// One of these must be called every frame.
|
||||
void SourceBlank();
|
||||
void SourceTexture(Draw::Texture *texture, int bufferWidth, int bufferHeight);
|
||||
void SourceFramebuffer(Draw::Framebuffer *fb, int bufferWidth, int bufferHeight);
|
||||
|
||||
|
||||
@@ -163,7 +163,6 @@ void VtxDec_Tu8_C5551_Ps16(const u8 *srcp, u8 *dstp, int numVerts, const UVScale
|
||||
__m128i lowbits = _mm_set1_epi32(0x00070707);
|
||||
|
||||
// Two vertices at a time, we can share some calculations.
|
||||
// It's OK to accidentally decode an extra vertex.
|
||||
while (count >= 2) {
|
||||
__m128i pos0 = _mm_loadl_epi64((const __m128i *) & src[0].x);
|
||||
__m128i pos1 = _mm_loadl_epi64((const __m128i *) & src[1].x);
|
||||
@@ -181,7 +180,7 @@ void VtxDec_Tu8_C5551_Ps16(const u8 *srcp, u8 *dstp, int numVerts, const UVScale
|
||||
__m128d uvf = _mm_castps_pd(_mm_add_ps(_mm_mul_ps(_mm_cvtepi32_ps(uv32), uvScale), uvOff));
|
||||
alpha &= col0;
|
||||
|
||||
// Combined RGBA
|
||||
// Combined 5551 -> 8888 RGBA. Nasty.
|
||||
__m128i col = _mm_set1_epi64x(col0);
|
||||
__m128i r = _mm_slli_epi32(_mm_and_si128(col, rmask), 8 - 5);
|
||||
__m128i g = _mm_slli_epi32(_mm_and_si128(col, gmask), 16 - 10);
|
||||
@@ -224,7 +223,6 @@ void VtxDec_Tu8_C5551_Ps16(const u8 *srcp, u8 *dstp, int numVerts, const UVScale
|
||||
uint32x2_t lowbits = vdup_n_u32(0x00070707);
|
||||
|
||||
// Two vertices at a time, we can share some calculations.
|
||||
// It's OK to accidentally decode an extra vertex.
|
||||
// Doing four vertices at a time might be even better, can share more of the pesky color format conversion.
|
||||
while (count >= 2) {
|
||||
int16x4_t pos0 = vld1_s16(&src[0].x);
|
||||
|
||||
@@ -125,6 +125,7 @@ public:
|
||||
|
||||
virtual void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) = 0;
|
||||
virtual void SetCurFramebufferDirty(bool dirty) = 0;
|
||||
virtual void PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config) = 0;
|
||||
virtual void CopyDisplayToOutput(const DisplayLayoutConfig &config) = 0;
|
||||
virtual bool PresentedThisFrame() const = 0;
|
||||
|
||||
|
||||
+6
-4
@@ -530,7 +530,7 @@ void GPUCommonHW::PreExecuteOp(u32 op, u32 diff) {
|
||||
CheckFlushOp(op >> 24, diff);
|
||||
}
|
||||
|
||||
void GPUCommonHW::CopyDisplayToOutput(const DisplayLayoutConfig &config) {
|
||||
void GPUCommonHW::PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config) {
|
||||
drawEngineCommon_->FlushQueuedDepth();
|
||||
// Flush anything left over.
|
||||
drawEngineCommon_->Flush();
|
||||
@@ -538,10 +538,12 @@ void GPUCommonHW::CopyDisplayToOutput(const DisplayLayoutConfig &config) {
|
||||
shaderManager_->DirtyLastShader();
|
||||
|
||||
// after this, render pass is active.
|
||||
framebufferManager_->CopyDisplayToOutput(config, curFramebufferDirty_);
|
||||
curFramebufferDirty_ = false;
|
||||
framebufferManager_->PrepareCopyDisplayToOutput(config, curFramebufferDirty_);
|
||||
}
|
||||
|
||||
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE);
|
||||
void GPUCommonHW::CopyDisplayToOutput(const DisplayLayoutConfig &config) {
|
||||
framebufferManager_->CopyDisplayToOutput(config);
|
||||
curFramebufferDirty_ = false;
|
||||
}
|
||||
|
||||
bool GPUCommonHW::PresentedThisFrame() const {
|
||||
|
||||
@@ -11,6 +11,7 @@ public:
|
||||
|
||||
// This can fail, and if so no render pass is active.
|
||||
void SetCurFramebufferDirty(bool dirty) override { curFramebufferDirty_ = dirty; }
|
||||
void PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config) override;
|
||||
void CopyDisplayToOutput(const DisplayLayoutConfig &config) override;
|
||||
void DoState(PointerWrap &p) override;
|
||||
void DeviceLost() override;
|
||||
|
||||
@@ -624,7 +624,7 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(const DisplayLayoutConfig &config,
|
||||
u1 = 1.0f;
|
||||
}
|
||||
if (!hasImage) {
|
||||
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "CopyToCurrentFboFromDisplayRam");
|
||||
presentation_->SourceBlank();
|
||||
presentation_->NotifyPresent();
|
||||
return;
|
||||
}
|
||||
@@ -644,13 +644,16 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(const DisplayLayoutConfig &config,
|
||||
|
||||
presentation_->SourceTexture(fbTex, desc.width, desc.height);
|
||||
presentation_->RunPostshaderPasses(config, outputFlags, config.iInternalScreenRotation, u0, v0, u1, v1);
|
||||
presentation_->CopyToOutput(config);
|
||||
}
|
||||
|
||||
void SoftGPU::CopyDisplayToOutput(const DisplayLayoutConfig &config) {
|
||||
void SoftGPU::PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config) {
|
||||
drawEngine_->transformUnit.Flush(this, "output");
|
||||
// The display always shows 480x272.
|
||||
CopyToCurrentFboFromDisplayRam(config, FB_WIDTH, FB_HEIGHT);
|
||||
}
|
||||
|
||||
void SoftGPU::CopyDisplayToOutput(const DisplayLayoutConfig &config) {
|
||||
presentation_->CopyToOutput(config);
|
||||
MarkDirty(displayFramebuf_, displayStride_, 272, displayFormat_, SoftGPUVRAMDirty::CLEAR);
|
||||
}
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ public:
|
||||
|
||||
void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override;
|
||||
void SetCurFramebufferDirty(bool dirty) override {}
|
||||
void PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config) override;
|
||||
void CopyDisplayToOutput(const DisplayLayoutConfig &config) override;
|
||||
void GetStats(char *buffer, size_t bufsize) override;
|
||||
std::vector<const VirtualFramebuffer *> GetFramebufferList() const override { return std::vector<const VirtualFramebuffer *>(); }
|
||||
|
||||
+1
-1
@@ -446,7 +446,7 @@ void DrawBackground(UIContext &dc, float alpha, Lin::Vec3 focus) {
|
||||
}
|
||||
|
||||
uint32_t GetBackgroundColorWithAlpha(const UIContext &dc) {
|
||||
return colorAlpha(colorBlend(dc.GetTheme().backgroundColor, 0, 0.5f), 0.65f); // 0.65 = 166 = A6
|
||||
return colorAlpha(colorBlend(dc.GetTheme().backgroundColor, 0, 0.5f), 0.72f); // 0.72 = 183 = B7
|
||||
}
|
||||
|
||||
enum class BackgroundFillMode {
|
||||
|
||||
+114
-215
@@ -93,6 +93,7 @@ using namespace std::placeholders;
|
||||
#include "UI/ControlMappingScreen.h"
|
||||
#include "UI/DisplayLayoutScreen.h"
|
||||
#include "UI/GameSettingsScreen.h"
|
||||
#include "UI/MiscViews.h"
|
||||
#include "UI/ProfilerDraw.h"
|
||||
#include "UI/DiscordIntegration.h"
|
||||
#include "UI/ChatScreen.h"
|
||||
@@ -286,7 +287,7 @@ void EmuScreen::ProcessGameBoot(const Path &filename) {
|
||||
g_OSD.Show(OSDType::MESSAGE_WARNING, "Shader cache is disabled (developer)");
|
||||
}
|
||||
|
||||
if (g_Config.bTiltInputEnabled && g_Config.iTiltInputType != 0) {
|
||||
if (g_Config.bTiltInputEnabled && g_Config.iTiltInputType != 0 && System_GetPropertyBool(SYSPROP_HAS_ACCELEROMETER)) {
|
||||
auto co = GetI18NCategory(I18NCat::CONTROLS);
|
||||
auto di = GetI18NCategory(I18NCat::DIALOG);
|
||||
g_OSD.Show(OSDType::MESSAGE_INFO, ApplySafeSubstitutions("%1: %2", co->T("Tilt control"), di->T("Enabled")), "", "I_CONTROLLER", 2.5f, "tilt");
|
||||
@@ -1231,52 +1232,6 @@ void EmuScreen::touch(const TouchInput &touch) {
|
||||
}
|
||||
}
|
||||
|
||||
class GameInfoBGView : public UI::InertView {
|
||||
public:
|
||||
GameInfoBGView(const Path &gamePath, UI::LayoutParams *layoutParams) : InertView(layoutParams), gamePath_(gamePath) {}
|
||||
|
||||
void Draw(UIContext &dc) override {
|
||||
// Should only be called when visible.
|
||||
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath_, GameInfoFlags::PIC1);
|
||||
dc.Flush();
|
||||
|
||||
// PIC1 is the loading image, so let's only draw if it's available.
|
||||
if (ginfo->Ready(GameInfoFlags::PIC1) && ginfo->pic1.texture) {
|
||||
Draw::Texture *texture = ginfo->pic1.texture;
|
||||
if (texture) {
|
||||
const DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
|
||||
// Similar to presentation, we want to put the game PIC1 in the same region of the screen.
|
||||
FRect frame = GetScreenFrame(config.bIgnoreScreenInsets, g_display.pixel_xres, g_display.pixel_yres);
|
||||
FRect rc;
|
||||
CalculateDisplayOutputRect(config, &rc, texture->Width(), texture->Height(), frame, config.iInternalScreenRotation);
|
||||
|
||||
// Need to adjust for DPI here since we're still in the UI coordinate space here, not the pixel coordinate space used for in-game presentation.
|
||||
Bounds bounds(rc.x * g_display.dpi_scale_x, rc.y * g_display.dpi_scale_y, rc.w * g_display.dpi_scale_x, rc.h * g_display.dpi_scale_y);
|
||||
|
||||
dc.GetDrawContext()->BindTexture(0, texture);
|
||||
|
||||
double loadTime = ginfo->pic1.timeLoaded;
|
||||
uint32_t color = alphaMul(color_, ease((time_now_d() - loadTime) * 3));
|
||||
dc.Draw()->DrawTexRect(bounds, 0, 0, 1, 1, color);
|
||||
dc.Flush();
|
||||
dc.RebindTexture();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string DescribeText() const override {
|
||||
return "";
|
||||
}
|
||||
|
||||
void SetColor(uint32_t c) {
|
||||
color_ = c;
|
||||
}
|
||||
|
||||
protected:
|
||||
Path gamePath_;
|
||||
uint32_t color_ = 0xFFC0C0C0;
|
||||
};
|
||||
|
||||
// TODO: Shouldn't actually need bounds for this, Anchor can center too.
|
||||
static UI::AnchorLayoutParams *AnchorInCorner(const Bounds &bounds, int corner, float xOffset, float yOffset) {
|
||||
using namespace UI;
|
||||
@@ -1567,7 +1522,7 @@ bool EmuScreen::checkPowerDown() {
|
||||
|
||||
ScreenRenderRole EmuScreen::renderRole(bool isTop) const {
|
||||
auto CanBeBackground = [&]() -> bool {
|
||||
if (g_Config.bSkipBufferEffects) {
|
||||
if (skipBufferEffects_) {
|
||||
return isTop || (g_Config.bTransparentBackground && ShouldRunBehind());
|
||||
}
|
||||
|
||||
@@ -1627,14 +1582,37 @@ void EmuScreen::HandleFlip() {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool EmuScreen::ShouldRunEmulation(ScreenRenderMode mode) const {
|
||||
if (!(mode & ScreenRenderMode::TOP) && !ShouldRunBehind() && strcmp(screenManager()->topScreen()->tag(), "DevMenu") != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ScreenRenderFlags EmuScreen::PreRender(ScreenRenderMode mode) {
|
||||
// If a boot is in progress, update it.
|
||||
ProcessGameBoot(gamePath_);
|
||||
|
||||
using namespace Draw;
|
||||
skipBufferEffects_ = g_Config.bSkipBufferEffects;
|
||||
if (!skipBufferEffects_) {
|
||||
if (ShouldRunEmulation(mode)) {
|
||||
// We need to run emulation here, and perform all the normal render passes.
|
||||
return RunEmulation(false);
|
||||
} else {
|
||||
const DeviceOrientation orientation = GetDeviceOrientation();
|
||||
const DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(orientation);
|
||||
// We run just the post shaders.
|
||||
gpu->PrepareCopyDisplayToOutput(displayLayoutConfig);
|
||||
}
|
||||
}
|
||||
return ScreenRenderFlags::NONE;
|
||||
}
|
||||
|
||||
ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) {
|
||||
// Moved from update, because we want it to be possible for booting to happen even when the screen
|
||||
// is in the background, like when choosing Reset from the pause menu.
|
||||
|
||||
// If a boot is in progress, update it.
|
||||
ProcessGameBoot(gamePath_);
|
||||
|
||||
const Draw::Viewport viewport{0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f};
|
||||
using namespace Draw;
|
||||
|
||||
DrawContext *draw = screenManager()->getDrawContext();
|
||||
@@ -1642,81 +1620,21 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) {
|
||||
return ScreenRenderFlags::NONE; // shouldn't really happen but I've seen a suspicious stack trace..
|
||||
}
|
||||
|
||||
ProcessQueuedVKeys();
|
||||
|
||||
const bool skipBufferEffects = g_Config.bSkipBufferEffects;
|
||||
|
||||
bool framebufferBound = false;
|
||||
|
||||
if (mode & ScreenRenderMode::FIRST) {
|
||||
// Actually, always gonna be first when it exists (?)
|
||||
|
||||
// Here we do NOT bind the backbuffer or clear the screen, unless non-buffered.
|
||||
// The emuscreen is different than the others - we really want to allow the game to render to framebuffers
|
||||
// before we ever bind the backbuffer for rendering. On mobile GPUs, switching back and forth between render
|
||||
// targets is a mortal sin so it's very important that we don't bind the backbuffer unnecessarily here.
|
||||
// We only bind it in FramebufferManager::CopyDisplayToOutput (unless non-buffered)...
|
||||
// We do, however, start the frame in other ways.
|
||||
|
||||
if (skipBufferEffects && !g_Config.bSoftwareRendering) {
|
||||
// We need to clear here already so that drawing during the frame is done on a clean slate.
|
||||
if (Core_IsStepping() && gpuStats.numFlips != 0) {
|
||||
draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::KEEP, RPAction::CLEAR, RPAction::CLEAR}, "EmuScreen_BackBuffer");
|
||||
} else {
|
||||
draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000}, "EmuScreen_BackBuffer");
|
||||
}
|
||||
|
||||
draw->SetViewport(viewport);
|
||||
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
|
||||
framebufferBound = true;
|
||||
}
|
||||
draw->SetTargetSize(g_display.pixel_xres, g_display.pixel_yres);
|
||||
} else {
|
||||
// Some other screen bound the backbuffer first.
|
||||
framebufferBound = true;
|
||||
}
|
||||
|
||||
g_OSD.NudgeIngameNotifications();
|
||||
|
||||
const DeviceOrientation orientation = GetDeviceOrientation();
|
||||
const DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(orientation);
|
||||
__DisplaySetDisplayLayoutConfig(displayLayoutConfig);
|
||||
ScreenRenderFlags screenRenderFlags = ScreenRenderFlags::NONE;
|
||||
|
||||
if (mode & ScreenRenderMode::TOP) {
|
||||
System_Notify(SystemNotification::KEEP_SCREEN_AWAKE);
|
||||
} else if (!ShouldRunBehind() && strcmp(screenManager()->topScreen()->tag(), "DevMenu") != 0) {
|
||||
// NOTE: The strcmp is != 0 - so all popped-over screens EXCEPT DevMenu
|
||||
// Just to make sure.
|
||||
if (PSP_IsInited() && !skipBufferEffects) {
|
||||
_dbg_assert_(gpu);
|
||||
gpu->BeginHostFrame(displayLayoutConfig);
|
||||
gpu->SetCurFramebufferDirty(true);
|
||||
}
|
||||
|
||||
const DeviceOrientation orientation = GetDeviceOrientation();
|
||||
const DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(orientation);
|
||||
|
||||
if (!skipBufferEffects_ && !ShouldRunEmulation(mode)) {
|
||||
if (gpu) {
|
||||
gpu->CopyDisplayToOutput(displayLayoutConfig);
|
||||
gpu->EndHostFrame();
|
||||
}
|
||||
if (gpu && gpu->PresentedThisFrame()) {
|
||||
framebufferBound = true;
|
||||
}
|
||||
if (!framebufferBound) {
|
||||
draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR,}, "EmuScreen_Behind");
|
||||
}
|
||||
|
||||
Draw::BackendState state = draw->GetCurrentBackendState();
|
||||
if (state.valid) {
|
||||
// The below can trigger when switching from skip-buffer-effects. We don't really care anymore...
|
||||
// _dbg_assert_msg_(state.passes >= 1, "skipB: %d sw: %d mode: %d back: %d tag: %s behi: %d", (int)skipBufferEffects, (int)g_Config.bSoftwareRendering, (int)mode, (int)g_Config.iGPUBackend, screenManager()->topScreen()->tag(), (int)g_Config.bRunBehindPauseMenu);
|
||||
// Workaround any remaining bugs like this.
|
||||
if (state.passes == 0) {
|
||||
draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR,}, "EmuScreen_SafeFallback");
|
||||
}
|
||||
}
|
||||
|
||||
// Need to make sure the UI texture is available, for "darken".
|
||||
screenManager()->getUIContext()->BeginFrame();
|
||||
draw->SetViewport(viewport);
|
||||
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
|
||||
darken();
|
||||
return ScreenRenderFlags::NONE;
|
||||
return screenRenderFlags;
|
||||
}
|
||||
|
||||
if (!PSP_IsInited() || readyToFinishBoot_) {
|
||||
@@ -1725,42 +1643,69 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) {
|
||||
if (mode & ScreenRenderMode::TOP) {
|
||||
checkPowerDown();
|
||||
}
|
||||
draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR}, "EmuScreen_Invalid");
|
||||
// Need to make sure the UI texture is available, for "darken".
|
||||
screenManager()->getUIContext()->BeginFrame();
|
||||
draw->SetViewport(viewport);
|
||||
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
|
||||
renderUI();
|
||||
return ScreenRenderFlags::NONE;
|
||||
return screenRenderFlags;
|
||||
}
|
||||
|
||||
// Freeze-frame functionality (loads a savestate on every frame).
|
||||
if (PSP_CoreParameter().freezeNext) {
|
||||
PSP_CoreParameter().frozen = true;
|
||||
PSP_CoreParameter().freezeNext = false;
|
||||
SaveState::SaveToRam(freezeState_);
|
||||
} else if (PSP_CoreParameter().frozen) {
|
||||
std::string errorString;
|
||||
if (CChunkFileReader::ERROR_NONE != SaveState::LoadFromRam(freezeState_, &errorString)) {
|
||||
ERROR_LOG(Log::SaveState, "Failed to load freeze state (%s). Unfreezing.", errorString.c_str());
|
||||
PSP_CoreParameter().frozen = false;
|
||||
}
|
||||
if (skipBufferEffects_) {
|
||||
// In skip buffer effects mode, we run emulation *after* the backbuffer bind.
|
||||
screenRenderFlags = RunEmulation(true);
|
||||
}
|
||||
|
||||
// We might have a bad viewport after RunEmulation, reset.
|
||||
Viewport viewport{0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f};
|
||||
draw->SetViewport(viewport);
|
||||
|
||||
ProcessQueuedVKeys();
|
||||
|
||||
const bool skipBufferEffects = skipBufferEffects_;
|
||||
|
||||
// Gotta copy the output at some point. Also this is where we take the screenshot if needed.
|
||||
if (gpu) {
|
||||
gpu->CopyDisplayToOutput(displayLayoutConfig);
|
||||
}
|
||||
|
||||
// Running it early allows things like direct readbacks of buffers, things we can't do
|
||||
// when we have started the final render pass. Well, technically we probably could with some manipulation
|
||||
// of pass order in the render managers..
|
||||
runImDebugger();
|
||||
|
||||
return RunEmulation(mode, framebufferBound, skipBufferEffects);
|
||||
Draw::BackendState state = draw->GetCurrentBackendState();
|
||||
|
||||
if (!(mode & ScreenRenderMode::TOP)) {
|
||||
renderImDebugger();
|
||||
// We're in run-behind mode, but we don't want to draw chat, debug UI and stuff. We do draw the imdebugger though.
|
||||
// So, darken and bail here.
|
||||
// Reset viewport/scissor to be sure.
|
||||
darken();
|
||||
return screenRenderFlags;
|
||||
}
|
||||
|
||||
// NOTE: We don't check for powerdown if we're not the top screen.
|
||||
checkPowerDown();
|
||||
|
||||
if (hasVisibleUI()) {
|
||||
cardboardDisableButton_->SetVisibility(displayLayoutConfig.bEnableCardboardVR ? UI::V_VISIBLE : UI::V_GONE);
|
||||
renderUI();
|
||||
}
|
||||
|
||||
if (chatMenu_ && (chatMenu_->GetVisibility() == UI::V_VISIBLE)) {
|
||||
SetVRAppMode(VRAppMode::VR_DIALOG_MODE);
|
||||
} else {
|
||||
SetVRAppMode(screenManager()->topScreen() == this ? VRAppMode::VR_GAME_MODE : VRAppMode::VR_DIALOG_MODE);
|
||||
}
|
||||
|
||||
renderImDebugger();
|
||||
return screenRenderFlags;
|
||||
}
|
||||
|
||||
ScreenRenderFlags EmuScreen::RunEmulation(ScreenRenderMode mode, bool framebufferBound, bool skipBufferEffects) {
|
||||
ScreenRenderFlags EmuScreen::RunEmulation(bool skipBufferEffects) {
|
||||
using namespace Draw;
|
||||
ScreenRenderFlags flags = ScreenRenderFlags::NONE;
|
||||
|
||||
g_OSD.NudgeIngameNotifications();
|
||||
|
||||
const DeviceOrientation orientation = GetDeviceOrientation();
|
||||
const DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(orientation);
|
||||
__DisplaySetDisplayLayoutConfig(displayLayoutConfig);
|
||||
|
||||
DrawContext *draw = screenManager()->getDrawContext();
|
||||
const Draw::Viewport viewport{0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f};
|
||||
|
||||
@@ -1768,15 +1713,26 @@ ScreenRenderFlags EmuScreen::RunEmulation(ScreenRenderMode mode, bool framebuffe
|
||||
bool blockedExecution = Achievements::IsBlockingExecution();
|
||||
uint32_t clearColor = 0;
|
||||
if (!blockedExecution) {
|
||||
// We process savestates before running the frame.
|
||||
SaveState::Process();
|
||||
|
||||
if (gpu) {
|
||||
gpu->BeginHostFrame(displayLayoutConfig);
|
||||
}
|
||||
if (SaveState::Process()) {
|
||||
// We might have lost the framebuffer bind if we had one, due to a readback.
|
||||
if (framebufferBound) {
|
||||
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_SavestateRebind");
|
||||
|
||||
// Freeze-frame functionality (loads a savestate on every frame).
|
||||
if (PSP_CoreParameter().freezeNext) {
|
||||
PSP_CoreParameter().frozen = true;
|
||||
PSP_CoreParameter().freezeNext = false;
|
||||
SaveState::SaveToRam(freezeState_);
|
||||
} else if (PSP_CoreParameter().frozen) {
|
||||
std::string errorString;
|
||||
if (CChunkFileReader::ERROR_NONE != SaveState::LoadFromRam(freezeState_, &errorString)) {
|
||||
ERROR_LOG(Log::SaveState, "Failed to load freeze state (%s). Unfreezing.", errorString.c_str());
|
||||
PSP_CoreParameter().frozen = false;
|
||||
}
|
||||
}
|
||||
|
||||
PSP_RunLoopWhileState();
|
||||
|
||||
// Hopefully, after running, coreState is now CORE_NEXTFRAME
|
||||
@@ -1796,17 +1752,13 @@ ScreenRenderFlags EmuScreen::RunEmulation(ScreenRenderMode mode, bool framebuffe
|
||||
// Clear to blue background screen
|
||||
bool dangerousSettings = !Reporting::IsSupported();
|
||||
clearColor = dangerousSettings ? 0xFF900050 : 0xFF900000;
|
||||
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_RuntimeError");
|
||||
framebufferBound = true;
|
||||
draw->Clear(Draw::Aspect::COLOR_BIT, clearColor, 0.0f, 0);
|
||||
// The info is drawn later in renderUI
|
||||
} else {
|
||||
// If we're stepping, it's convenient not to clear the screen entirely, so we copy display to output.
|
||||
// This won't work in non-buffered, but that's fine.
|
||||
if (!framebufferBound && PSP_IsInited()) {
|
||||
// draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_Stepping");
|
||||
if (PSP_IsInited()) {
|
||||
gpu->SetCurFramebufferDirty(true);
|
||||
gpu->CopyDisplayToOutput(displayLayoutConfig);
|
||||
framebufferBound = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1823,7 +1775,15 @@ ScreenRenderFlags EmuScreen::RunEmulation(ScreenRenderMode mode, bool framebuffe
|
||||
}
|
||||
|
||||
if (gpu) {
|
||||
// Run post processing and other passes.
|
||||
gpu->PrepareCopyDisplayToOutput(displayLayoutConfig);
|
||||
gpu->EndHostFrame();
|
||||
|
||||
// The right time to run this
|
||||
if (SaveState::ProcessScreenshot(skipBufferEffects) && skipBufferEffects) {
|
||||
// Need to restore the backbuffer render pass, it probably got destroyed.
|
||||
draw->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR}, "BackBuffer");
|
||||
}
|
||||
}
|
||||
|
||||
if (SaveState::PollRestartNeeded() && !bootPending_) {
|
||||
@@ -1850,58 +1810,6 @@ ScreenRenderFlags EmuScreen::RunEmulation(ScreenRenderMode mode, bool framebuffe
|
||||
}
|
||||
}
|
||||
|
||||
if (gpu && gpu->PresentedThisFrame()) {
|
||||
framebufferBound = true;
|
||||
}
|
||||
|
||||
if (!framebufferBound) {
|
||||
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_NoFrame");
|
||||
draw->SetViewport(viewport);
|
||||
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
|
||||
}
|
||||
|
||||
Draw::BackendState state = draw->GetCurrentBackendState();
|
||||
|
||||
// State.valid just states whether the passes parameter has a meaningful value.
|
||||
if (state.valid) {
|
||||
_dbg_assert_msg_(state.passes >= 1, "skipB: %d sw: %d mode: %d back: %d bound: %d", (int)skipBufferEffects, (int)g_Config.bSoftwareRendering, (int)mode, (int)g_Config.iGPUBackend, (int)framebufferBound);
|
||||
if (state.passes == 0) {
|
||||
// Workaround any remaining bugs like this.
|
||||
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, }, "EmuScreen_SafeFallback");
|
||||
}
|
||||
}
|
||||
|
||||
screenManager()->getUIContext()->BeginFrame();
|
||||
|
||||
if (!(mode & ScreenRenderMode::TOP)) {
|
||||
renderImDebugger();
|
||||
// We're in run-behind mode, but we don't want to draw chat, debug UI and stuff. We do draw the imdebugger though.
|
||||
// So, darken and bail here.
|
||||
// Reset viewport/scissor to be sure.
|
||||
draw->SetViewport(viewport);
|
||||
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
|
||||
darken();
|
||||
return flags;
|
||||
}
|
||||
|
||||
// NOTE: We don't check for powerdown if we're not the top screen.
|
||||
if (checkPowerDown()) {
|
||||
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_PowerDown");
|
||||
}
|
||||
|
||||
if (hasVisibleUI()) {
|
||||
draw->SetViewport(viewport);
|
||||
cardboardDisableButton_->SetVisibility(displayLayoutConfig.bEnableCardboardVR ? UI::V_VISIBLE : UI::V_GONE);
|
||||
renderUI();
|
||||
}
|
||||
|
||||
if (chatMenu_ && (chatMenu_->GetVisibility() == UI::V_VISIBLE)) {
|
||||
SetVRAppMode(VRAppMode::VR_DIALOG_MODE);
|
||||
} else {
|
||||
SetVRAppMode(screenManager()->topScreen() == this ? VRAppMode::VR_GAME_MODE : VRAppMode::VR_DIALOG_MODE);
|
||||
}
|
||||
|
||||
renderImDebugger();
|
||||
return flags;
|
||||
}
|
||||
|
||||
@@ -2017,21 +1925,12 @@ bool EmuScreen::hasVisibleUI() {
|
||||
void EmuScreen::renderUI() {
|
||||
using namespace Draw;
|
||||
|
||||
DrawContext *thin3d = screenManager()->getDrawContext();
|
||||
DrawContext *draw = screenManager()->getDrawContext();
|
||||
UIContext *ctx = screenManager()->getUIContext();
|
||||
ctx->BeginFrame();
|
||||
// This sets up some important states but not the viewport.
|
||||
ctx->Begin();
|
||||
|
||||
Viewport viewport;
|
||||
viewport.TopLeftX = 0;
|
||||
viewport.TopLeftY = 0;
|
||||
viewport.Width = g_display.pixel_xres;
|
||||
viewport.Height = g_display.pixel_yres;
|
||||
viewport.MaxDepth = 1.0;
|
||||
viewport.MinDepth = 0.0;
|
||||
thin3d->SetViewport(viewport);
|
||||
|
||||
if (root_) {
|
||||
UI::LayoutViewHierarchy(*ctx, RootMargins(), root_, false, false);
|
||||
root_->Draw(*ctx);
|
||||
|
||||
+5
-1
@@ -70,10 +70,11 @@ public:
|
||||
protected:
|
||||
void darken();
|
||||
void focusChanged(ScreenFocusChange focusChange) override;
|
||||
ScreenRenderFlags PreRender(ScreenRenderMode mode) override;
|
||||
|
||||
private:
|
||||
void CreateViews() override;
|
||||
ScreenRenderFlags RunEmulation(ScreenRenderMode mode, bool framebufferBound, bool skipBufferEffects);
|
||||
ScreenRenderFlags RunEmulation(bool skipBufferEffects);
|
||||
void OnDevTools(UI::EventParams ¶ms);
|
||||
void OnChat(UI::EventParams ¶ms);
|
||||
|
||||
@@ -95,6 +96,8 @@ private:
|
||||
void ProcessQueuedVKeys();
|
||||
void ProcessVKey(VirtKey vkey);
|
||||
|
||||
bool ShouldRunEmulation(ScreenRenderMode mode) const;
|
||||
|
||||
UI::Event OnDevMenu;
|
||||
UI::Event OnChatMenu;
|
||||
bool bootPending_ = true;
|
||||
@@ -159,6 +162,7 @@ private:
|
||||
#endif
|
||||
bool autoLoadFailed_ = false; // to prevent repeat reloads
|
||||
bool readyToFinishBoot_ = false;
|
||||
bool skipBufferEffects_ = false; // cached state, fetched once per frame.
|
||||
};
|
||||
|
||||
bool MustRunBehind();
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
#include "Core/HLE/sceUsbMic.h"
|
||||
#include "Core/HLE/sceUtility.h"
|
||||
#include "GPU/Common/PostShader.h"
|
||||
#include "GPU/GPU.h"
|
||||
|
||||
#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
|
||||
#include "UI/DarwinFileSystemServices.h"
|
||||
@@ -499,6 +500,7 @@ void GameSettingsScreen::CreateGraphicsSettings(UI::ViewGroup *graphicsSettings)
|
||||
if (g_Config.bSkipBufferEffects) {
|
||||
g_Config.bAutoFrameSkip = false;
|
||||
}
|
||||
|
||||
System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);
|
||||
});
|
||||
skipBufferEffects->SetDisabledPtr(&g_Config.bSoftwareRendering);
|
||||
|
||||
@@ -98,12 +98,6 @@ void HandleCommonMessages(UIMessage message, const char *value, ScreenManager *m
|
||||
}
|
||||
|
||||
ScreenRenderFlags BackgroundScreen::render(ScreenRenderMode mode) {
|
||||
if (mode & ScreenRenderMode::FIRST) {
|
||||
SetupViewport();
|
||||
} else {
|
||||
_dbg_assert_(false);
|
||||
}
|
||||
|
||||
UIContext *uiContext = screenManager()->getUIContext();
|
||||
|
||||
uiContext->PushTransform({ translation_, scale_, alpha_ });
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "UI/GameInfoCache.h"
|
||||
#include "Common/UI/PopupScreens.h"
|
||||
#include "Core/Config.h"
|
||||
#include "GPU/Common/PresentationCommon.h"
|
||||
|
||||
TextWithImage::TextWithImage(ImageID imageID, std::string_view text, UI::LinearLayoutParams *layoutParams) : UI::LinearLayout(ORIENT_HORIZONTAL, layoutParams) {
|
||||
using namespace UI;
|
||||
@@ -259,3 +260,32 @@ void AddRotationPicker(ScreenManager *screenManager, UI::ViewGroup *parent, bool
|
||||
System_Notify(SystemNotification::ROTATE_UPDATED);
|
||||
});
|
||||
}
|
||||
|
||||
void GameInfoBGView::Draw(UIContext &dc) {
|
||||
// Should only be called when visible.
|
||||
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath_, GameInfoFlags::PIC1);
|
||||
dc.Flush();
|
||||
|
||||
// PIC1 is the loading image, so let's only draw if it's available.
|
||||
if (ginfo->Ready(GameInfoFlags::PIC1) && ginfo->pic1.texture) {
|
||||
Draw::Texture *texture = ginfo->pic1.texture;
|
||||
if (texture) {
|
||||
const DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
|
||||
// Similar to presentation, we want to put the game PIC1 in the same region of the screen.
|
||||
FRect frame = GetScreenFrame(config.bIgnoreScreenInsets, g_display.pixel_xres, g_display.pixel_yres);
|
||||
FRect rc;
|
||||
CalculateDisplayOutputRect(config, &rc, texture->Width(), texture->Height(), frame, config.iInternalScreenRotation);
|
||||
|
||||
// Need to adjust for DPI here since we're still in the UI coordinate space here, not the pixel coordinate space used for in-game presentation.
|
||||
Bounds bounds(rc.x * g_display.dpi_scale_x, rc.y * g_display.dpi_scale_y, rc.w * g_display.dpi_scale_x, rc.h * g_display.dpi_scale_y);
|
||||
|
||||
dc.GetDrawContext()->BindTexture(0, texture);
|
||||
|
||||
double loadTime = ginfo->pic1.timeLoaded;
|
||||
uint32_t color = alphaMul(color_, ease((time_now_d() - loadTime) * 3));
|
||||
dc.Draw()->DrawTexRect(bounds, 0, 0, 1, 1, color);
|
||||
dc.Flush();
|
||||
dc.RebindTexture();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,4 +78,17 @@ private:
|
||||
float scale_ = 1.0f;
|
||||
};
|
||||
|
||||
class GameInfoBGView : public UI::InertView {
|
||||
public:
|
||||
GameInfoBGView(const Path &gamePath, UI::LayoutParams *layoutParams) : InertView(layoutParams), gamePath_(gamePath) {}
|
||||
|
||||
void Draw(UIContext &dc) override;
|
||||
std::string DescribeText() const override { return ""; }
|
||||
void SetColor(uint32_t c) { color_ = c; }
|
||||
|
||||
protected:
|
||||
Path gamePath_;
|
||||
uint32_t color_ = 0xFFC0C0C0;
|
||||
};
|
||||
|
||||
void AddRotationPicker(ScreenManager *screenManager, UI::ViewGroup *parent, bool text);
|
||||
+1
-1
@@ -1151,7 +1151,7 @@ void NativeFrame(GraphicsContext *graphicsContext) {
|
||||
|
||||
g_screenManager->getUIContext()->SetTintSaturation(g_Config.fUITint, g_Config.fUISaturation);
|
||||
|
||||
// All actual rendering happen in here.
|
||||
// All actual rendering (and also emulation) happens in here.
|
||||
ScreenRenderFlags renderFlags = g_screenManager->render();
|
||||
if (g_screenManager->getUIContext()->Text()) {
|
||||
g_screenManager->getUIContext()->Text()->OncePerFrame();
|
||||
|
||||
@@ -266,10 +266,11 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const
|
||||
}
|
||||
|
||||
if (draw) {
|
||||
draw->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "Headless");
|
||||
draw->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "Backbuffer");
|
||||
// Vulkan may get angry if we don't do a final present.
|
||||
if (gpu) {
|
||||
gpu->SetCurFramebufferDirty(true);
|
||||
gpu->PrepareCopyDisplayToOutput(g_Config.GetDisplayLayoutConfig(DeviceOrientation::Landscape));
|
||||
gpu->CopyDisplayToOutput(g_Config.GetDisplayLayoutConfig(DeviceOrientation::Landscape));
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user