Remove preRender/postRender methods from screens, in favor of a mode parameter.

This commit is contained in:
Henrik Rydgård 2023-12-10 14:09:55 +01:00
parent 22295a6412
commit 0ff0ad9140
8 changed files with 122 additions and 129 deletions

View file

@ -155,52 +155,48 @@ void ScreenManager::resized() {
void ScreenManager::render() {
if (!stack_.empty()) {
switch (stack_.back().flags) {
case LAYER_TRANSPARENT:
if (stack_.size() == 1) {
ERROR_LOG(SYSTEM, "Can't have sidemenu over nothing");
break;
} else {
auto last = stack_.end();
auto iter = last;
iter--;
while (iter->flags == LAYER_TRANSPARENT) {
iter--;
}
auto first = iter;
_assert_(iter->screen);
// Collect the screens to render
TinySet<Screen *, 6> layers;
// TODO: Make really sure that this "mismatched" pre/post only happens
// when screens are "compatible" (both are UIScreens, for example).
first->screen->preRender();
while (iter < last) {
iter->screen->render(ScreenRenderMode::TOP);
iter++;
}
stack_.back().screen->render(ScreenRenderMode::TOP);
if (overlayScreen_) {
overlayScreen_->render(ScreenRenderMode::TOP);
}
if (postRenderCb_) {
// Really can't render anything after this! Will crash the screenshot mechanism if we do.
postRenderCb_(getUIContext(), postRenderUserdata_);
}
first->screen->postRender();
break;
// Start at the end, collect screens to form the transparency stack.
// Then we'll iterate them in reverse order.
// Note that we skip the overlay screen, we handle it separately.
bool foundCoveringScreen = false; // Note, can be separate from background screen!
auto iter = stack_.end();
do {
--iter;
if (!foundCoveringScreen) {
layers.push_back(iter->screen);
}
default:
_assert_(stack_.back().screen);
stack_.back().screen->preRender();
stack_.back().screen->render(ScreenRenderMode::TOP);
if (overlayScreen_) {
overlayScreen_->render(ScreenRenderMode::TOP);
if (iter->flags != LAYER_TRANSPARENT) {
foundCoveringScreen = true;
}
if (postRenderCb_) {
// Really can't render anything after this! Will crash the screenshot mechanism if we do.
postRenderCb_(getUIContext(), postRenderUserdata_);
} while (iter != stack_.begin());
// OK, now we iterate backwards over our little pile of screens.
bool first = true;
for (int i = (int)layers.size() - 1; i >= 0; i--) {
ScreenRenderMode mode = ScreenRenderMode::DEFAULT;
if (i == (int)layers.size() - 1) {
// Bottom.
mode = ScreenRenderMode::FIRST;
} else if (i == 0) {
mode = ScreenRenderMode::TOP;
} else {
mode = ScreenRenderMode::BEHIND;
}
stack_.back().screen->postRender();
break;
layers[i]->render(mode);
}
if (overlayScreen_) {
// It doesn't care about mode.
overlayScreen_->render(ScreenRenderMode::TOP);
}
if (postRenderCb_) {
// Really can't render anything after this! Will crash the screenshot mechanism if we do.
postRenderCb_(getUIContext(), postRenderUserdata_);
}
} else {
ERROR_LOG(SYSTEM, "No current screen!");

View file

@ -48,6 +48,7 @@ enum class ScreenFocusChange {
};
enum class ScreenRenderMode {
DEFAULT = 0,
FIRST = 1,
BACKGROUND = 2,
BEHIND = 4,
@ -64,9 +65,7 @@ public:
virtual void onFinish(DialogResult reason) {}
virtual void update() {}
virtual void preRender() {}
virtual void render(ScreenRenderMode mode) {}
virtual void postRender() {}
virtual void resized() {}
virtual void dialogFinished(const Screen *dialog, DialogResult result) {}
virtual void sendMessage(UIMessage message, const char *value) {}

View file

@ -193,30 +193,26 @@ void UIScreen::deviceRestored() {
root_->DeviceRestored(screenManager()->getDrawContext());
}
void UIScreen::preRender() {
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);
}
void UIScreen::postRender() {
screenManager()->getUIContext()->Flush();
}
void UIScreen::render(ScreenRenderMode mode) {
if (mode & ScreenRenderMode::FIRST) {
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);
}
DoRecreateViews();
if (root_) {
@ -235,6 +231,10 @@ void UIScreen::render(ScreenRenderMode mode) {
uiContext->PopTransform();
}
if (mode & ScreenRenderMode::TOP) {
screenManager()->getUIContext()->Flush();
}
}
TouchInput UIScreen::transformTouch(const TouchInput &touch) {

View file

@ -36,9 +36,7 @@ public:
~UIScreen();
void update() override;
void preRender() override;
void render(ScreenRenderMode mode) override;
void postRender() override;
void deviceLost() override;
void deviceRestored() override;

View file

@ -1409,45 +1409,39 @@ static void DrawFPS(UIContext *ctx, const Bounds &bounds) {
ctx->RebindTexture();
}
void EmuScreen::preRender() {
using namespace Draw;
DrawContext *draw = screenManager()->getDrawContext();
// 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 ((g_Config.bSkipBufferEffects && !g_Config.bSoftwareRendering) || Core_IsStepping()) {
// 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::DONT_CARE, RPAction::DONT_CARE }, "EmuScreen_BackBuffer");
} else {
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }, "EmuScreen_BackBuffer");
}
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);
}
void EmuScreen::postRender() {
Draw::DrawContext *draw = screenManager()->getDrawContext();
if (!draw)
return;
if (stopRender_)
draw->WipeQueue();
}
void EmuScreen::render(ScreenRenderMode mode) {
if (mode == ScreenRenderMode::FIRST) {
// Actually, always gonna be first (?)
using namespace Draw;
DrawContext *draw = screenManager()->getDrawContext();
// 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 ((g_Config.bSkipBufferEffects && !g_Config.bSoftwareRendering) || Core_IsStepping()) {
// 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::DONT_CARE, RPAction::DONT_CARE }, "EmuScreen_BackBuffer");
} else {
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }, "EmuScreen_BackBuffer");
}
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);
}
using namespace Draw;
DrawContext *thin3d = screenManager()->getDrawContext();
@ -1552,6 +1546,11 @@ void EmuScreen::render(ScreenRenderMode mode) {
} else {
SetVRAppMode(screenManager()->topScreen() == this ? VRAppMode::VR_GAME_MODE : VRAppMode::VR_DIALOG_MODE);
}
if (mode & ScreenRenderMode::TOP) {
if (stopRender_)
thin3d->WipeQueue();
}
}
bool EmuScreen::hasVisibleUI() {

View file

@ -43,8 +43,6 @@ public:
void update() override;
void render(ScreenRenderMode mode) override;
void preRender() override;
void postRender() override;
void dialogFinished(const Screen *dialog, DialogResult result) override;
void sendMessage(UIMessage message, const char *value) override;
void resized() override;

View file

@ -166,26 +166,29 @@ ReportScreen::ReportScreen(const Path &gamePath)
ratingEnabled_ = enableReporting_;
}
void ReportScreen::postRender() {
// We do this after render because we need it to be within the frame (so the screenshot works).
// We could do it mid frame, but then we have to reapply viewport/scissor.
if (!tookScreenshot_) {
Path path = GetSysDirectory(DIRECTORY_SCREENSHOT);
if (!File::Exists(path)) {
File::CreateDir(path);
}
screenshotFilename_ = path / ".reporting.jpg";
if (TakeGameScreenshot(screenshotFilename_, ScreenshotFormat::JPG, SCREENSHOT_DISPLAY, nullptr, nullptr, 4)) {
// Redo the views already, now with a screenshot included.
RecreateViews();
} else {
// Good news (?), the views are good as-is without a screenshot.
screenshotFilename_.clear();
}
tookScreenshot_ = true;
}
void ReportScreen::render(ScreenRenderMode mode) {
UIScreen::render(mode);
UIDialogScreenWithGameBackground::postRender();
if (mode & ScreenRenderMode::TOP) {
// We do this after render because we need it to be within the frame (so the screenshot works).
// We could do it mid frame, but then we have to reapply viewport/scissor.
if (!tookScreenshot_) {
Path path = GetSysDirectory(DIRECTORY_SCREENSHOT);
if (!File::Exists(path)) {
File::CreateDir(path);
}
screenshotFilename_ = path / ".reporting.jpg";
if (TakeGameScreenshot(screenshotFilename_, ScreenshotFormat::JPG, SCREENSHOT_DISPLAY, nullptr, nullptr, 4)) {
// Redo the views already, now with a screenshot included.
RecreateViews();
} else {
// Good news (?), the views are good as-is without a screenshot.
screenshotFilename_.clear();
}
tookScreenshot_ = true;
}
}
}
void ReportScreen::update() {

View file

@ -40,7 +40,7 @@ public:
const char *tag() const override { return "Report"; }
protected:
void postRender() override;
void render(ScreenRenderMode mode) override;
void update() override;
void resized() override;
void CreateViews() override;