/* ** viewport.cpp ** ** This file is part of mkxp. ** ** Copyright (C) 2013 - 2021 Amaryllis Kulla ** ** mkxp is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 2 of the License, or ** (at your option) any later version. ** ** mkxp is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with mkxp. If not, see . */ #include "viewport.h" #include "sharedstate.h" #include "etc.h" #include "util.h" #include "quad.h" #include "glstate.h" #include "graphics.h" #include "sigslot/signal.hpp" #ifdef MKXPZ_RETRO # include "sandbox-serial-util.h" #endif // MKXPZ_RETRO #define GUARD_V(value, expression) do { expression; if (exception.is_error()) return value; } while (0) #define GUARD(expression) GUARD_V(, expression) #ifdef MKXPZ_RETRO static uint64_t next_id = 1; #endif // MKXPZ_RETRO struct ViewportPrivate { /* Needed for geometry changes */ Viewport *self; Rect *rect; sigslot::connection rectCon; #ifdef MKXPZ_RETRO Rect deserSavedRect; bool deserScreenRectChanged; #endif // MKXPZ_RETRO Color *color; Tone *tone; IntRect screenRect; bool isOnScreen; EtcTemps tmp; ViewportPrivate(int x, int y, int width, int height, Viewport *self) : self(self), rect(&tmp.rect), color(&tmp.color), tone(&tmp.tone), isOnScreen(false) { rect->set(x, y, width, height); updateRectCon(); } ~ViewportPrivate() { rectCon.disconnect(); } void onRectChange() { self->geometry.rect = rect->toIntRect(); self->notifyGeometryChange(); recomputeOnScreen(); } void updateRectCon() { rectCon.disconnect(); rectCon = rect->valueChanged.connect (&ViewportPrivate::onRectChange, this); } void recomputeOnScreen() { isOnScreen = screenRect.x < rect->x + rect->width && rect->x < screenRect.x + screenRect.w && screenRect.y < rect->y + rect->height && rect->y < screenRect.y + screenRect.h; } bool needsEffectRender(bool flashing) { bool rectEffective = !rect->isEmpty(); bool colorToneEffective = color->hasEffect() || tone->hasEffect() || flashing; return (rectEffective && colorToneEffective && isOnScreen); } }; Viewport::Viewport(int x, int y, int width, int height) : SceneElement(*shState->screen()), #ifdef MKXPZ_RETRO id(next_id++), #endif // MKXPZ_RETRO sceneLink(this) { initViewport(x, y, width, height); } Viewport::Viewport(Rect *rect) : SceneElement(*shState->screen()), #ifdef MKXPZ_RETRO id(next_id++), #endif // MKXPZ_RETRO sceneLink(this) { initViewport(rect->x, rect->y, rect->width, rect->height); } Viewport::Viewport() : SceneElement(*shState->screen()), #ifdef MKXPZ_RETRO id(next_id++), #endif // MKXPZ_RETRO sceneLink(this) { const Graphics &graphics = shState->graphics(); initViewport(0, 0, graphics.width(), graphics.height()); } void Viewport::initViewport(int x, int y, int width, int height) { p = new ViewportPrivate(x, y, width, height, this); /* Set our own geometry */ geometry.rect = IntRect(x, y, width, height); /* Handle parent geometry */ onGeometryChange(scene->getGeometry()); } Viewport::~Viewport() { dispose(); } void Viewport::update(Exception &exception) { GUARD(guardDisposed(exception)); GUARD(Flashable::update(exception)); } DEF_ATTR_RD_SIMPLE(Viewport, OX, int, geometry.orig.x) DEF_ATTR_RD_SIMPLE(Viewport, OY, int, geometry.orig.y) DEF_ATTR_SIMPLE(Viewport, Rect, Rect&, *p->rect) DEF_ATTR_SIMPLE(Viewport, Color, Color&, *p->color) DEF_ATTR_SIMPLE(Viewport, Tone, Tone&, *p->tone) void Viewport::setOX(Exception &exception, int value) { GUARD(guardDisposed(exception)); if (geometry.orig.x == value) return; geometry.orig.x = value; notifyGeometryChange(); } void Viewport::setOY(Exception &exception, int value) { GUARD(guardDisposed(exception)); if (geometry.orig.y == value) return; geometry.orig.y = value; notifyGeometryChange(); } void Viewport::initDynAttribs() { p->rect = new Rect(*p->rect); p->color = new Color; p->tone = new Tone; p->updateRectCon(); } /* Scene */ void Viewport::composite(Exception &exception) { if (emptyFlashFlag) return; bool renderEffect = p->needsEffectRender(flashing); if (elements.getSize() == 0 && !renderEffect) return; /* Setup scissor */ glState.scissorTest.pushSet(true); glState.scissorBox.pushSet(p->rect->toIntRect()); GUARD(Scene::composite(exception)); /* If any effects are visible, request parent Scene to * render them. */ if (renderEffect) scene->requestViewportRender (p->color->norm, flashColor, p->tone->norm); glState.scissorBox.pop(); glState.scissorTest.pop(); } /* SceneElement */ void Viewport::draw(Exception &exception) { GUARD(composite(exception)); } void Viewport::onGeometryChange(const Geometry &geo) { p->screenRect = geo.rect; p->recomputeOnScreen(); } void Viewport::releaseResources() { unlink(); delete p; } #ifdef MKXPZ_RETRO bool Viewport::sandbox_serialize(void *&data, mkxp_sandbox::wasm_size_t &max_size) const { if (!mkxp_sandbox::sandbox_serialize(p->screenRect, data, max_size)) return false; if (!sandbox_serialize_scene_element(data, max_size)) return false; if (!mkxp_sandbox::sandbox_serialize(p->rect == &p->tmp.rect ? nullptr : p->rect, data, max_size)) return false; if (!mkxp_sandbox::sandbox_serialize(p->color == &p->tmp.color ? nullptr : p->color, data, max_size)) return false; if (!mkxp_sandbox::sandbox_serialize(p->tone == &p->tmp.tone ? nullptr : p->tone, data, max_size)) return false; return true; } bool Viewport::sandbox_deserialize(const void *&data, mkxp_sandbox::wasm_size_t &max_size) { { IntRect value = p->screenRect; if (!mkxp_sandbox::sandbox_deserialize(p->screenRect, data, max_size)) return false; if (p->screenRect != value) { p->deserScreenRectChanged = true; } } if (!sandbox_deserialize_scene_element(data, max_size)) return false; if (!mkxp_sandbox::sandbox_deserialize(p->rect, data, max_size)) return false; if (p->rect == nullptr) { p->rect = &p->tmp.rect; } if (!mkxp_sandbox::sandbox_deserialize(p->color, data, max_size)) return false; if (p->color == nullptr) { p->color = &p->tmp.color; } if (!mkxp_sandbox::sandbox_deserialize(p->tone, data, max_size)) return false; if (p->tone == nullptr) { p->tone = &p->tmp.tone; } return true; } void Viewport::sandbox_deserialize_begin() { sandbox_deserialize_begin_scene_element(); if (isDisposed()) return; p->rectCon.disconnect(); if (p->rect != nullptr) { p->deserSavedRect = *p->rect; } else { p->deserSavedRect.set(0, 0, 0, 0); } p->deserScreenRectChanged = false; } void Viewport::sandbox_deserialize_end() { if (isDisposed()) return; sandbox_deserialize_end_scene_element(); if (isDisposed()) return; if (p->rect != nullptr) { p->rectCon = p->rect->valueChanged.connect(&ViewportPrivate::onRectChange, p); if (*p->rect != p->deserSavedRect) { p->onRectChange(); } } if (isDisposed()) return; if (p->deserScreenRectChanged) { p->recomputeOnScreen(); } } #endif // MXKPZ_RETRO ViewportElement::ViewportElement(void (*dispose)(void *), Viewport *viewport, int z, int spriteY) : SceneElement(viewport ? *viewport : *shState->screen(), z, spriteY), m_dispose(dispose), m_viewport(viewport) { if (rgssVer == 1 && viewport) viewportDispCon = viewport->wasDisposed.connect(&ViewportElement::viewportElementDisposal, this); } Viewport *ViewportElement::getViewport() const { return m_viewport; } void ViewportElement::setViewport(Viewport *viewport) { m_viewport = viewport; viewportDispCon.disconnect(); if (rgssVer == 1 && viewport) viewportDispCon = viewport->wasDisposed.connect(&ViewportElement::viewportElementDisposal, this); setScene(viewport ? *viewport : *shState->screen()); onViewportChange(); onGeometryChange(scene->getGeometry()); } void ViewportElement::viewportElementDisposal() { viewportDispCon.disconnect(); if(m_dispose != nullptr) m_dispose(this); } ViewportElement::~ViewportElement() { viewportDispCon.disconnect(); } #ifdef MKXPZ_RETRO bool ViewportElement::sandbox_serialize_viewport_element(void *&data, mkxp_sandbox::wasm_size_t &max_size) const { if (!sandbox_serialize_scene_element(data, max_size)) return false; if (!mkxp_sandbox::sandbox_serialize(m_viewport, data, max_size)) return false; return true; } bool ViewportElement::sandbox_deserialize_viewport_element(const void *&data, mkxp_sandbox::wasm_size_t &max_size) { if (!sandbox_deserialize_scene_element(data, max_size)) return false; if (!mkxp_sandbox::sandbox_deserialize(m_viewport, data, max_size)) return false; return true; } void ViewportElement::sandbox_deserialize_begin_viewport_element() { sandbox_deserialize_begin_scene_element(); viewportDispCon.disconnect(); deserSavedViewportId = m_viewport == nullptr ? 0 : m_viewport->id; } void ViewportElement::sandbox_deserialize_end_viewport_element() { if (m_viewport != nullptr) { if (rgssVer == 1) { viewportDispCon = m_viewport->wasDisposed.connect(&ViewportElement::viewportElementDisposal, this); if (m_viewport->isDisposed()) { viewportElementDisposal(); } } } if ((m_viewport != nullptr && m_viewport->id != deserSavedViewportId) || (m_viewport == nullptr && deserSavedViewportId != 0)) { if (!deserSceneElementWasUnlinked) { unlink(); } scene = m_viewport == nullptr ? shState->screen() : m_viewport; onViewportChange(); } sandbox_deserialize_end_scene_element(); } #endif // MXKPZ_RETRO