2019-11-23 20:27:39 +08:00
|
|
|
/****************************************************************************
|
|
|
|
Copyright (c) 2009 Jason Booth
|
|
|
|
Copyright (c) 2010-2012 cocos2d-x.org
|
|
|
|
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
|
|
|
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
2022-12-12 19:41:07 +08:00
|
|
|
Copyright (c) 2022 Bytedance Inc.
|
2019-11-23 20:27:39 +08:00
|
|
|
|
2022-10-01 16:24:52 +08:00
|
|
|
https://axmolengine.github.io/
|
2019-11-23 20:27:39 +08:00
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
|
|
in the Software without restriction, including without limitation the rights
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
|
|
all copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
THE SOFTWARE.
|
|
|
|
****************************************************************************/
|
2023-06-11 13:08:08 +08:00
|
|
|
#include "2d/RenderTexture.h"
|
2019-11-23 20:27:39 +08:00
|
|
|
|
2023-06-11 13:08:08 +08:00
|
|
|
#include "base/Utils.h"
|
|
|
|
#include "platform/FileUtils.h"
|
|
|
|
#include "base/EventType.h"
|
|
|
|
#include "base/Configuration.h"
|
|
|
|
#include "base/Director.h"
|
|
|
|
#include "base/EventListenerCustom.h"
|
|
|
|
#include "base/EventDispatcher.h"
|
|
|
|
#include "renderer/Renderer.h"
|
|
|
|
#include "2d/Camera.h"
|
|
|
|
#include "renderer/TextureCache.h"
|
2019-11-23 20:27:39 +08:00
|
|
|
#include "renderer/backend/Device.h"
|
|
|
|
#include "renderer/backend/Texture.h"
|
2020-09-21 22:10:50 +08:00
|
|
|
#include "renderer/backend/RenderTarget.h"
|
2019-11-23 20:27:39 +08:00
|
|
|
|
2022-07-11 17:50:21 +08:00
|
|
|
NS_AX_BEGIN
|
2019-11-23 20:27:39 +08:00
|
|
|
|
|
|
|
// implementation RenderTexture
|
|
|
|
RenderTexture::RenderTexture()
|
|
|
|
{
|
2022-07-16 10:43:05 +08:00
|
|
|
#if AX_ENABLE_CACHE_TEXTURE_DATA
|
2019-11-23 20:27:39 +08:00
|
|
|
// Listen this event to save render texture before come to background.
|
|
|
|
// Then it can be restored after coming to foreground on Android.
|
2021-12-25 10:04:45 +08:00
|
|
|
auto toBackgroundListener =
|
2022-07-16 10:43:05 +08:00
|
|
|
EventListenerCustom::create(EVENT_COME_TO_BACKGROUND, AX_CALLBACK_1(RenderTexture::listenToBackground, this));
|
2019-11-23 20:27:39 +08:00
|
|
|
_eventDispatcher->addEventListenerWithSceneGraphPriority(toBackgroundListener, this);
|
|
|
|
|
2021-12-25 10:04:45 +08:00
|
|
|
auto toForegroundListener =
|
2022-07-16 10:43:05 +08:00
|
|
|
EventListenerCustom::create(EVENT_COME_TO_FOREGROUND, AX_CALLBACK_1(RenderTexture::listenToForeground, this));
|
2019-11-23 20:27:39 +08:00
|
|
|
_eventDispatcher->addEventListenerWithSceneGraphPriority(toForegroundListener, this);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
RenderTexture::~RenderTexture()
|
|
|
|
{
|
2022-07-16 10:43:05 +08:00
|
|
|
AX_SAFE_RELEASE(_renderTarget);
|
|
|
|
AX_SAFE_RELEASE(_sprite);
|
|
|
|
AX_SAFE_RELEASE(_depthStencilTexture);
|
|
|
|
AX_SAFE_RELEASE(_UITextureImage);
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void RenderTexture::listenToBackground(EventCustom* /*event*/)
|
|
|
|
{
|
|
|
|
// We have not found a way to dispatch the enter background message before the texture data are destroyed.
|
|
|
|
// So we disable this pair of message handler at present.
|
2022-07-16 10:43:05 +08:00
|
|
|
#if AX_ENABLE_CACHE_TEXTURE_DATA
|
2019-11-23 20:27:39 +08:00
|
|
|
// to get the rendered texture data
|
2021-12-25 10:04:45 +08:00
|
|
|
auto func = [&](Image* uiTextureImage) {
|
2019-11-23 20:27:39 +08:00
|
|
|
if (uiTextureImage)
|
|
|
|
{
|
2022-07-16 10:43:05 +08:00
|
|
|
AX_SAFE_RELEASE(_UITextureImage);
|
2019-11-23 20:27:39 +08:00
|
|
|
_UITextureImage = uiTextureImage;
|
2022-07-16 10:43:05 +08:00
|
|
|
AX_SAFE_RETAIN(_UITextureImage);
|
2021-10-23 23:27:14 +08:00
|
|
|
const Vec2& s = _texture2D->getContentSizeInPixels();
|
2021-12-25 10:04:45 +08:00
|
|
|
VolatileTextureMgr::addDataTexture(_texture2D, uiTextureImage->getData(), s.width * s.height * 4,
|
|
|
|
backend::PixelFormat::RGBA8, s);
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-07-16 10:43:05 +08:00
|
|
|
AXLOG("Cache rendertexture failed!");
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
2022-07-16 10:43:05 +08:00
|
|
|
AX_SAFE_RELEASE(uiTextureImage);
|
2019-11-23 20:27:39 +08:00
|
|
|
};
|
|
|
|
auto callback = std::bind(func, std::placeholders::_1);
|
|
|
|
newImage(callback, false);
|
|
|
|
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void RenderTexture::listenToForeground(EventCustom* /*event*/)
|
|
|
|
{
|
2022-07-16 10:43:05 +08:00
|
|
|
#if AX_ENABLE_CACHE_TEXTURE_DATA
|
2021-10-23 23:27:14 +08:00
|
|
|
const Vec2& s = _texture2D->getContentSizeInPixels();
|
2021-12-25 10:04:45 +08:00
|
|
|
// TODO new-renderer: field _depthAndStencilFormat removal
|
|
|
|
// if (_depthAndStencilFormat != 0)
|
|
|
|
// {
|
|
|
|
// setupDepthAndStencil(s.width, s.height);
|
|
|
|
// }
|
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
_texture2D->setAntiAliasTexParameters();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2022-06-28 17:33:00 +08:00
|
|
|
RenderTexture* RenderTexture::create(int w, int h, backend::PixelFormat eFormat, bool sharedRenderTarget)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
2021-12-25 10:04:45 +08:00
|
|
|
RenderTexture* ret = new RenderTexture();
|
2019-11-23 20:27:39 +08:00
|
|
|
|
2022-06-28 17:33:00 +08:00
|
|
|
if (ret->initWithWidthAndHeight(w, h, eFormat, sharedRenderTarget))
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
|
|
|
ret->autorelease();
|
|
|
|
return ret;
|
|
|
|
}
|
2022-07-16 10:43:05 +08:00
|
|
|
AX_SAFE_DELETE(ret);
|
2019-11-23 20:27:39 +08:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2022-06-28 17:33:00 +08:00
|
|
|
RenderTexture* RenderTexture::create(int w, int h, backend::PixelFormat eFormat, PixelFormat uDepthStencilFormat, bool sharedRenderTarget)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
2021-12-25 10:04:45 +08:00
|
|
|
RenderTexture* ret = new RenderTexture();
|
2019-11-23 20:27:39 +08:00
|
|
|
|
2022-06-28 17:33:00 +08:00
|
|
|
if (ret->initWithWidthAndHeight(w, h, eFormat, uDepthStencilFormat, sharedRenderTarget))
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
|
|
|
ret->autorelease();
|
|
|
|
return ret;
|
|
|
|
}
|
2022-07-16 10:43:05 +08:00
|
|
|
AX_SAFE_DELETE(ret);
|
2019-11-23 20:27:39 +08:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2022-06-28 17:33:00 +08:00
|
|
|
RenderTexture* RenderTexture::create(int w, int h, bool sharedRenderTarget)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
2021-12-25 10:04:45 +08:00
|
|
|
RenderTexture* ret = new RenderTexture();
|
2019-11-23 20:27:39 +08:00
|
|
|
|
2022-06-28 17:33:00 +08:00
|
|
|
if (ret->initWithWidthAndHeight(w, h, backend::PixelFormat::RGBA8, PixelFormat::NONE, sharedRenderTarget))
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
|
|
|
ret->autorelease();
|
|
|
|
return ret;
|
|
|
|
}
|
2022-07-16 10:43:05 +08:00
|
|
|
AX_SAFE_DELETE(ret);
|
2019-11-23 20:27:39 +08:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2022-06-28 17:33:00 +08:00
|
|
|
bool RenderTexture::initWithWidthAndHeight(int w, int h, backend::PixelFormat eFormat, bool sharedRenderTarget)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
2022-06-28 17:33:00 +08:00
|
|
|
return initWithWidthAndHeight(w, h, eFormat, PixelFormat::NONE, sharedRenderTarget);
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
|
2022-06-28 17:33:00 +08:00
|
|
|
bool RenderTexture::initWithWidthAndHeight(int w,
|
|
|
|
int h,
|
|
|
|
backend::PixelFormat format,
|
|
|
|
PixelFormat depthStencilFormat,
|
|
|
|
bool sharedRenderTarget)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
2023-08-31 18:07:23 +08:00
|
|
|
AXASSERT(format != backend::PixelFormat::A8, "only RGB and RGBA formats are valid for a render texture");
|
2019-11-23 20:27:39 +08:00
|
|
|
|
|
|
|
bool ret = false;
|
|
|
|
do
|
|
|
|
{
|
2021-12-25 10:04:45 +08:00
|
|
|
_fullRect = _rtTextureRect = Rect(0, 0, w, h);
|
2022-07-16 10:43:05 +08:00
|
|
|
w = (int)(w * AX_CONTENT_SCALE_FACTOR());
|
|
|
|
h = (int)(h * AX_CONTENT_SCALE_FACTOR());
|
2021-12-25 10:04:45 +08:00
|
|
|
_fullviewPort = Rect(0, 0, w, h);
|
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
// textures must be power of two squared
|
|
|
|
int powW = 0;
|
|
|
|
int powH = 0;
|
|
|
|
|
|
|
|
if (Configuration::getInstance()->supportsNPOT())
|
|
|
|
{
|
|
|
|
powW = w;
|
|
|
|
powH = h;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
powW = ccNextPOT(w);
|
|
|
|
powH = ccNextPOT(h);
|
|
|
|
}
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
backend::TextureDescriptor descriptor;
|
2021-12-25 10:04:45 +08:00
|
|
|
descriptor.width = powW;
|
|
|
|
descriptor.height = powH;
|
|
|
|
descriptor.textureUsage = TextureUsage::RENDER_TARGET;
|
2020-09-25 11:07:56 +08:00
|
|
|
descriptor.textureFormat = PixelFormat::RGBA8;
|
2021-12-25 10:04:45 +08:00
|
|
|
_texture2D = new Texture2D();
|
2022-07-16 10:43:05 +08:00
|
|
|
_texture2D->updateTextureDescriptor(descriptor, !!AX_ENABLE_PREMULTIPLIED_ALPHA);
|
2019-11-23 20:27:39 +08:00
|
|
|
_renderTargetFlags = RenderTargetFlag::COLOR;
|
|
|
|
|
2022-12-12 19:41:07 +08:00
|
|
|
if (PixelFormat::D24S8 == depthStencilFormat || sharedRenderTarget)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
2021-12-25 10:04:45 +08:00
|
|
|
_renderTargetFlags = RenderTargetFlag::ALL;
|
2022-12-12 19:41:07 +08:00
|
|
|
descriptor.textureFormat = PixelFormat::D24S8;
|
2019-11-23 20:27:39 +08:00
|
|
|
|
2023-04-28 14:54:57 +08:00
|
|
|
AX_SAFE_RELEASE(_depthStencilTexture);
|
|
|
|
|
2021-12-08 00:11:53 +08:00
|
|
|
_depthStencilTexture = new Texture2D();
|
2020-09-25 15:04:55 +08:00
|
|
|
_depthStencilTexture->updateTextureDescriptor(descriptor);
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
|
2022-07-16 10:43:05 +08:00
|
|
|
AX_SAFE_RELEASE(_renderTarget);
|
2022-06-28 17:33:00 +08:00
|
|
|
|
|
|
|
if (sharedRenderTarget)
|
|
|
|
{
|
|
|
|
_renderTarget = _director->getRenderer()->getOffscreenRenderTarget();
|
|
|
|
_renderTarget->retain();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_renderTarget = backend::Device::getInstance()->newRenderTarget(
|
|
|
|
_renderTargetFlags, _texture2D ? _texture2D->getBackendTexture() : nullptr,
|
|
|
|
_depthStencilTexture ? _depthStencilTexture->getBackendTexture() : nullptr,
|
|
|
|
_depthStencilTexture ? _depthStencilTexture->getBackendTexture() : nullptr);
|
|
|
|
}
|
2022-06-24 14:18:48 +08:00
|
|
|
|
|
|
|
_renderTarget->setColorAttachment(_texture2D ? _texture2D->getBackendTexture() : nullptr);
|
|
|
|
|
|
|
|
auto depthStencilTexture = _depthStencilTexture ? _depthStencilTexture->getBackendTexture() : nullptr;
|
|
|
|
_renderTarget->setDepthAttachment(depthStencilTexture);
|
|
|
|
_renderTarget->setStencilAttachment(depthStencilTexture);
|
2020-09-21 22:10:50 +08:00
|
|
|
|
|
|
|
clearColorAttachment();
|
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
_texture2D->setAntiAliasTexParameters();
|
|
|
|
|
|
|
|
// retained
|
|
|
|
setSprite(Sprite::createWithTexture(_texture2D));
|
|
|
|
|
2022-11-10 21:22:55 +08:00
|
|
|
#if defined(AX_USE_GL)
|
2019-11-23 20:27:39 +08:00
|
|
|
_sprite->setFlippedY(true);
|
|
|
|
#endif
|
|
|
|
|
2021-12-25 10:04:45 +08:00
|
|
|
if (_texture2D->hasPremultipliedAlpha())
|
|
|
|
{
|
2020-11-16 12:21:27 +08:00
|
|
|
_sprite->setBlendFunc(BlendFunc::ALPHA_PREMULTIPLIED);
|
|
|
|
_sprite->setOpacityModifyRGB(true);
|
|
|
|
}
|
2021-12-25 10:04:45 +08:00
|
|
|
else
|
|
|
|
{
|
2020-11-16 12:21:27 +08:00
|
|
|
_sprite->setBlendFunc(BlendFunc::ALPHA_NON_PREMULTIPLIED);
|
|
|
|
_sprite->setOpacityModifyRGB(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
_texture2D->release();
|
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
// Disabled by default.
|
|
|
|
_autoDraw = false;
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
// add sprite for backward compatibility
|
|
|
|
addChild(_sprite);
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
ret = true;
|
|
|
|
} while (0);
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void RenderTexture::setSprite(Sprite* sprite)
|
|
|
|
{
|
2022-07-16 10:43:05 +08:00
|
|
|
#if AX_ENABLE_GC_FOR_NATIVE_OBJECTS
|
2019-11-23 20:27:39 +08:00
|
|
|
auto sEngine = ScriptEngineManager::getInstance()->getScriptEngine();
|
|
|
|
if (sEngine)
|
|
|
|
{
|
|
|
|
if (sprite)
|
|
|
|
sEngine->retainScriptObject(this, sprite);
|
|
|
|
if (_sprite)
|
|
|
|
sEngine->releaseScriptObject(this, _sprite);
|
|
|
|
}
|
2022-07-16 10:43:05 +08:00
|
|
|
#endif // AX_ENABLE_GC_FOR_NATIVE_OBJECTS
|
2023-04-28 14:54:57 +08:00
|
|
|
if (_sprite)
|
|
|
|
{
|
|
|
|
_sprite->removeFromParent();
|
|
|
|
_sprite->release();
|
|
|
|
}
|
|
|
|
|
2022-07-16 10:43:05 +08:00
|
|
|
AX_SAFE_RETAIN(sprite);
|
2019-11-23 20:27:39 +08:00
|
|
|
_sprite = sprite;
|
|
|
|
}
|
|
|
|
|
|
|
|
void RenderTexture::setVirtualViewport(const Vec2& rtBegin, const Rect& fullRect, const Rect& fullViewport)
|
|
|
|
{
|
|
|
|
_rtTextureRect.origin.x = rtBegin.x;
|
|
|
|
_rtTextureRect.origin.y = rtBegin.y;
|
|
|
|
|
|
|
|
_fullRect = fullRect;
|
|
|
|
|
|
|
|
_fullviewPort = fullViewport;
|
|
|
|
}
|
|
|
|
|
2022-06-28 17:33:00 +08:00
|
|
|
bool RenderTexture::isSharedRenderTarget() const
|
|
|
|
{
|
|
|
|
return _renderTarget == _director->getRenderer()->getOffscreenRenderTarget();
|
|
|
|
}
|
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
void RenderTexture::beginWithClear(float r, float g, float b, float a)
|
|
|
|
{
|
|
|
|
beginWithClear(r, g, b, a, 0, 0, ClearFlag::COLOR);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RenderTexture::beginWithClear(float r, float g, float b, float a, float depthValue)
|
|
|
|
{
|
|
|
|
beginWithClear(r, g, b, a, depthValue, 0, ClearFlag::COLOR | ClearFlag::DEPTH);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RenderTexture::beginWithClear(float r, float g, float b, float a, float depthValue, int stencilValue)
|
|
|
|
{
|
|
|
|
beginWithClear(r, g, b, a, depthValue, stencilValue, ClearFlag::ALL);
|
|
|
|
}
|
|
|
|
|
2021-12-25 10:04:45 +08:00
|
|
|
void RenderTexture::beginWithClear(float r,
|
|
|
|
float g,
|
|
|
|
float b,
|
|
|
|
float a,
|
|
|
|
float depthValue,
|
|
|
|
int stencilValue,
|
|
|
|
ClearFlag flags)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
|
|
|
setClearColor(Color4F(r, g, b, a));
|
|
|
|
setClearDepth(depthValue);
|
|
|
|
setClearStencil(stencilValue);
|
|
|
|
setClearFlags(flags);
|
|
|
|
begin();
|
2020-09-13 19:16:59 +08:00
|
|
|
_director->getRenderer()->clear(_clearFlags, _clearColor, _clearDepth, _clearStencil, _globalZOrder);
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void RenderTexture::clear(float r, float g, float b, float a)
|
|
|
|
{
|
|
|
|
this->beginWithClear(r, g, b, a);
|
|
|
|
this->end();
|
|
|
|
}
|
|
|
|
|
|
|
|
void RenderTexture::clearDepth(float depthValue)
|
|
|
|
{
|
|
|
|
setClearDepth(depthValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RenderTexture::clearStencil(int stencilValue)
|
|
|
|
{
|
|
|
|
setClearStencil(stencilValue);
|
|
|
|
}
|
|
|
|
|
2021-12-25 10:04:45 +08:00
|
|
|
void RenderTexture::visit(Renderer* renderer, const Mat4& parentTransform, uint32_t parentFlags)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
|
|
|
// override visit.
|
|
|
|
// Don't call visit on its children
|
|
|
|
if (!_visible)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
uint32_t flags = processParentFlags(parentTransform, parentFlags);
|
|
|
|
|
|
|
|
// IMPORTANT:
|
|
|
|
// To ease the migration to v3.0, we still support the Mat4 stack,
|
|
|
|
// but it is deprecated and your code should not rely on it
|
2020-09-13 19:16:59 +08:00
|
|
|
_director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
|
|
|
|
_director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform);
|
2019-11-23 20:27:39 +08:00
|
|
|
|
|
|
|
_sprite->visit(renderer, _modelViewTransform, flags);
|
|
|
|
if (isVisitableByVisitingCamera())
|
|
|
|
{
|
|
|
|
draw(renderer, _modelViewTransform, flags);
|
|
|
|
}
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2020-09-13 19:16:59 +08:00
|
|
|
_director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
|
2019-11-23 20:27:39 +08:00
|
|
|
|
|
|
|
// FIX ME: Why need to set _orderOfArrival to 0??
|
|
|
|
// Please refer to https://github.com/cocos2d/cocos2d-x/pull/6920
|
|
|
|
// setOrderOfArrival(0);
|
|
|
|
}
|
|
|
|
|
2021-12-31 12:12:40 +08:00
|
|
|
bool RenderTexture::saveToFileAsNonPMA(std::string_view filename, bool isRGBA, SaveFileCallbackType callback)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
|
|
|
std::string basename(filename);
|
|
|
|
std::transform(basename.begin(), basename.end(), basename.begin(), ::tolower);
|
|
|
|
|
|
|
|
if (basename.find(".png") != std::string::npos)
|
|
|
|
{
|
|
|
|
return saveToFileAsNonPMA(filename, Image::Format::PNG, isRGBA, callback);
|
|
|
|
}
|
|
|
|
else if (basename.find(".jpg") != std::string::npos)
|
|
|
|
{
|
2021-12-25 10:04:45 +08:00
|
|
|
if (isRGBA)
|
2022-07-16 10:43:05 +08:00
|
|
|
AXLOG("RGBA is not supported for JPG format.");
|
2019-11-23 20:27:39 +08:00
|
|
|
return saveToFileAsNonPMA(filename, Image::Format::JPG, false, callback);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-07-16 10:43:05 +08:00
|
|
|
AXLOG("Only PNG and JPG format are supported now!");
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return saveToFileAsNonPMA(filename, Image::Format::JPG, false, callback);
|
2021-12-25 10:04:45 +08:00
|
|
|
}
|
|
|
|
|
2021-12-31 12:12:40 +08:00
|
|
|
bool RenderTexture::saveToFile(std::string_view filename, bool isRGBA, SaveFileCallbackType callback)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
|
|
|
std::string basename(filename);
|
|
|
|
std::transform(basename.begin(), basename.end(), basename.begin(), ::tolower);
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
if (basename.find(".png") != std::string::npos)
|
|
|
|
{
|
|
|
|
return saveToFile(filename, Image::Format::PNG, isRGBA, callback);
|
|
|
|
}
|
|
|
|
else if (basename.find(".jpg") != std::string::npos)
|
|
|
|
{
|
2021-12-25 10:04:45 +08:00
|
|
|
if (isRGBA)
|
2022-07-16 10:43:05 +08:00
|
|
|
AXLOG("RGBA is not supported for JPG format.");
|
2019-11-23 20:27:39 +08:00
|
|
|
return saveToFile(filename, Image::Format::JPG, false, callback);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-07-16 10:43:05 +08:00
|
|
|
AXLOG("Only PNG and JPG format are supported now!");
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
return saveToFile(filename, Image::Format::JPG, false, callback);
|
|
|
|
}
|
|
|
|
|
2021-12-31 12:12:40 +08:00
|
|
|
bool RenderTexture::saveToFileAsNonPMA(std::string_view fileName,
|
2021-12-25 10:04:45 +08:00
|
|
|
Image::Format format,
|
|
|
|
bool isRGBA,
|
2021-12-31 12:12:40 +08:00
|
|
|
SaveFileCallbackType callback)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
2022-07-16 10:43:05 +08:00
|
|
|
AXASSERT(format == Image::Format::JPG || format == Image::Format::PNG,
|
2021-12-25 10:04:45 +08:00
|
|
|
"the image can only be saved as JPG or PNG format");
|
|
|
|
if (isRGBA && format == Image::Format::JPG)
|
2022-07-16 10:43:05 +08:00
|
|
|
AXLOG("RGBA is not supported for JPG format");
|
2019-11-23 20:27:39 +08:00
|
|
|
|
|
|
|
_saveFileCallback = callback;
|
|
|
|
|
2021-12-31 12:12:40 +08:00
|
|
|
std::string fullpath = FileUtils::getInstance()->getWritablePath().append(fileName);
|
2019-11-23 20:27:39 +08:00
|
|
|
|
2022-06-24 14:18:48 +08:00
|
|
|
auto renderer = _director->getRenderer();
|
|
|
|
auto saveToFileCommand = renderer->nextCallbackCommand();
|
|
|
|
saveToFileCommand->init(_globalZOrder);
|
2023-01-03 23:02:17 +08:00
|
|
|
saveToFileCommand->func = AX_CALLBACK_0(RenderTexture::onSaveToFile, this, std::move(fullpath), isRGBA, true);
|
2022-06-24 14:18:48 +08:00
|
|
|
|
|
|
|
renderer->addCommand(saveToFileCommand);
|
2019-11-23 20:27:39 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-12-31 12:12:40 +08:00
|
|
|
bool RenderTexture::saveToFile(std::string_view fileName,
|
2021-12-25 10:04:45 +08:00
|
|
|
Image::Format format,
|
|
|
|
bool isRGBA,
|
2022-06-24 14:18:48 +08:00
|
|
|
SaveFileCallbackType callback)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
2022-07-16 10:43:05 +08:00
|
|
|
AXASSERT(format == Image::Format::JPG || format == Image::Format::PNG,
|
2019-11-23 20:27:39 +08:00
|
|
|
"the image can only be saved as JPG or PNG format");
|
2021-12-25 10:04:45 +08:00
|
|
|
if (isRGBA && format == Image::Format::JPG)
|
2022-07-16 10:43:05 +08:00
|
|
|
AXLOG("RGBA is not supported for JPG format");
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
_saveFileCallback = callback;
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2021-12-31 12:12:40 +08:00
|
|
|
std::string fullpath = FileUtils::getInstance()->getWritablePath().append(fileName);
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2022-06-24 14:18:48 +08:00
|
|
|
auto renderer = _director->getRenderer();
|
|
|
|
auto saveToFileCommand = renderer->nextCallbackCommand();
|
|
|
|
saveToFileCommand->init(_globalZOrder);
|
2023-01-03 23:02:17 +08:00
|
|
|
saveToFileCommand->func = AX_CALLBACK_0(RenderTexture::onSaveToFile, this, std::move(fullpath), isRGBA, false);
|
2022-06-24 14:18:48 +08:00
|
|
|
|
|
|
|
_director->getRenderer()->addCommand(saveToFileCommand);
|
2019-11-23 20:27:39 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-01-03 23:02:17 +08:00
|
|
|
void RenderTexture::onSaveToFile(std::string filename, bool isRGBA, bool forceNonPMA)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
2023-01-03 23:02:17 +08:00
|
|
|
auto callbackFunc = [this, _filename = std::move(filename), isRGBA, forceNonPMA](RefPtr<Image> image) {
|
2019-11-23 20:27:39 +08:00
|
|
|
if (image)
|
|
|
|
{
|
|
|
|
if (forceNonPMA && image->hasPremultipliedAlpha())
|
|
|
|
{
|
|
|
|
image->reversePremultipliedAlpha();
|
|
|
|
}
|
2023-01-03 23:02:17 +08:00
|
|
|
image->saveToFile(_filename, !isRGBA);
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
2021-12-25 10:04:45 +08:00
|
|
|
if (_saveFileCallback)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
2023-01-03 23:02:17 +08:00
|
|
|
_saveFileCallback(this, _filename);
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
newImage(callbackFunc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* get buffer as Image */
|
2020-09-11 11:57:55 +08:00
|
|
|
void RenderTexture::newImage(std::function<void(RefPtr<Image>)> imageCallback, bool flipImage)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
2022-07-16 10:43:05 +08:00
|
|
|
AXASSERT(_pixelFormat == backend::PixelFormat::RGBA8, "only RGBA8888 can be saved as image");
|
2019-11-23 20:27:39 +08:00
|
|
|
|
|
|
|
if ((nullptr == _texture2D))
|
|
|
|
{
|
2021-12-25 10:04:45 +08:00
|
|
|
return;
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
|
2021-10-23 23:27:14 +08:00
|
|
|
const Vec2& s = _texture2D->getContentSizeInPixels();
|
2019-11-23 20:27:39 +08:00
|
|
|
|
|
|
|
// to get the image size to save
|
|
|
|
// if the saving image domain exceeds the buffer texture domain,
|
|
|
|
// it should be cut
|
2021-12-25 10:04:45 +08:00
|
|
|
int savedBufferWidth = (int)s.width;
|
|
|
|
int savedBufferHeight = (int)s.height;
|
2020-06-12 11:24:10 +08:00
|
|
|
bool hasPremultipliedAlpha = _texture2D->hasPremultipliedAlpha();
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2020-09-21 22:10:50 +08:00
|
|
|
_director->getRenderer()->readPixels(_renderTarget, [=](const backend::PixelBufferDescriptor& pbd) {
|
2021-12-25 10:04:45 +08:00
|
|
|
if (pbd)
|
|
|
|
{
|
|
|
|
auto image = utils::makeInstance<Image>(&Image::initWithRawData, pbd._data.getBytes(), pbd._data.getSize(),
|
|
|
|
pbd._width, pbd._height, 8, hasPremultipliedAlpha);
|
2020-06-12 11:24:10 +08:00
|
|
|
imageCallback(image);
|
2020-09-10 21:14:28 +08:00
|
|
|
}
|
2021-12-25 10:04:45 +08:00
|
|
|
else
|
|
|
|
imageCallback(nullptr);
|
2020-09-13 13:27:50 +08:00
|
|
|
});
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
|
2021-12-25 10:04:45 +08:00
|
|
|
void RenderTexture::draw(Renderer* renderer, const Mat4& transform, uint32_t flags)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
|
|
|
if (_autoDraw)
|
|
|
|
{
|
2021-12-25 10:04:45 +08:00
|
|
|
// Begin will create a render group using new render target
|
2019-11-23 20:27:39 +08:00
|
|
|
begin();
|
|
|
|
|
2021-12-25 10:04:45 +08:00
|
|
|
// clear screen
|
2021-04-22 22:01:47 +08:00
|
|
|
_director->getRenderer()->clear(_clearFlags, _clearColor, _clearDepth, _clearStencil, _globalZOrder);
|
2019-11-23 20:27:39 +08:00
|
|
|
|
|
|
|
//! make sure all children are drawn
|
|
|
|
sortAllChildren();
|
|
|
|
|
2021-12-25 10:04:45 +08:00
|
|
|
for (const auto& child : _children)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
|
|
|
if (child != _sprite)
|
|
|
|
child->visit(renderer, transform, flags);
|
|
|
|
}
|
|
|
|
|
2021-12-25 10:04:45 +08:00
|
|
|
// End will pop the current render group
|
2019-11-23 20:27:39 +08:00
|
|
|
end();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RenderTexture::onBegin()
|
|
|
|
{
|
2021-04-22 22:01:47 +08:00
|
|
|
_oldProjMatrix = _director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
|
|
|
|
_director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, _projectionMatrix);
|
2019-11-23 20:27:39 +08:00
|
|
|
|
2021-04-22 22:01:47 +08:00
|
|
|
_oldTransMatrix = _director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
|
|
|
|
_director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _transformMatrix);
|
2019-11-23 20:27:39 +08:00
|
|
|
|
2021-12-25 10:04:45 +08:00
|
|
|
if (!_keepMatrix)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
2021-04-22 22:01:47 +08:00
|
|
|
_director->setProjection(_director->getProjection());
|
2021-10-23 23:27:14 +08:00
|
|
|
const Vec2& texSize = _texture2D->getContentSizeInPixels();
|
2019-11-23 20:27:39 +08:00
|
|
|
|
|
|
|
// Calculate the adjustment ratios based on the old and new projections
|
2021-12-25 10:04:45 +08:00
|
|
|
Vec2 size = _director->getWinSizeInPixels();
|
|
|
|
float widthRatio = size.width / texSize.width;
|
2019-11-23 20:27:39 +08:00
|
|
|
float heightRatio = size.height / texSize.height;
|
|
|
|
|
|
|
|
Mat4 orthoMatrix;
|
2021-12-25 10:04:45 +08:00
|
|
|
Mat4::createOrthographicOffCenter((float)-1.0 / widthRatio, (float)1.0 / widthRatio, (float)-1.0 / heightRatio,
|
|
|
|
(float)1.0 / heightRatio, -1, 1, &orthoMatrix);
|
2021-04-22 22:01:47 +08:00
|
|
|
_director->multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, orthoMatrix);
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
Rect viewport;
|
2021-12-25 10:04:45 +08:00
|
|
|
viewport.size.width = _fullviewPort.size.width;
|
|
|
|
viewport.size.height = _fullviewPort.size.height;
|
|
|
|
float viewPortRectWidthRatio = float(viewport.size.width) / _fullRect.size.width;
|
|
|
|
float viewPortRectHeightRatio = float(viewport.size.height) / _fullRect.size.height;
|
|
|
|
viewport.origin.x = (_fullRect.origin.x - _rtTextureRect.origin.x) * viewPortRectWidthRatio;
|
|
|
|
viewport.origin.y = (_fullRect.origin.y - _rtTextureRect.origin.y) * viewPortRectHeightRatio;
|
|
|
|
|
|
|
|
Renderer* renderer = _director->getRenderer();
|
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
_oldViewport = renderer->getViewport();
|
|
|
|
renderer->setViewPort(viewport.origin.x, viewport.origin.y, viewport.size.width, viewport.size.height);
|
|
|
|
|
2020-09-21 22:10:50 +08:00
|
|
|
_oldRenderTarget = renderer->getRenderTarget();
|
|
|
|
renderer->setRenderTarget(_renderTarget);
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void RenderTexture::onEnd()
|
|
|
|
{
|
2021-04-22 22:01:47 +08:00
|
|
|
_director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, _oldProjMatrix);
|
|
|
|
_director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _oldTransMatrix);
|
2021-12-25 10:04:45 +08:00
|
|
|
|
|
|
|
Renderer* renderer = _director->getRenderer();
|
2023-08-31 18:07:23 +08:00
|
|
|
renderer->setViewPort(_oldViewport.x, _oldViewport.y, _oldViewport.w, _oldViewport.h);
|
2020-09-21 22:10:50 +08:00
|
|
|
|
|
|
|
renderer->setRenderTarget(_oldRenderTarget);
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void RenderTexture::begin()
|
|
|
|
{
|
2020-09-13 19:16:59 +08:00
|
|
|
_director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
|
|
|
|
_projectionMatrix = _director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2020-09-13 19:16:59 +08:00
|
|
|
_director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
|
|
|
|
_transformMatrix = _director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
|
2021-12-25 10:04:45 +08:00
|
|
|
|
|
|
|
if (!_keepMatrix)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
2020-09-13 19:16:59 +08:00
|
|
|
_director->setProjection(_director->getProjection());
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2021-10-23 23:27:14 +08:00
|
|
|
const Vec2& texSize = _texture2D->getContentSizeInPixels();
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
// Calculate the adjustment ratios based on the old and new projections
|
2021-10-23 23:27:14 +08:00
|
|
|
Vec2 size = _director->getWinSizeInPixels();
|
2021-12-25 10:04:45 +08:00
|
|
|
|
|
|
|
float widthRatio = size.width / texSize.width;
|
2019-11-23 20:27:39 +08:00
|
|
|
float heightRatio = size.height / texSize.height;
|
2021-12-25 10:04:45 +08:00
|
|
|
|
2019-11-23 20:27:39 +08:00
|
|
|
Mat4 orthoMatrix;
|
2021-12-25 10:04:45 +08:00
|
|
|
Mat4::createOrthographicOffCenter((float)-1.0 / widthRatio, (float)1.0 / widthRatio, (float)-1.0 / heightRatio,
|
|
|
|
(float)1.0 / heightRatio, -1, 1, &orthoMatrix);
|
2020-09-13 19:16:59 +08:00
|
|
|
_director->multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, orthoMatrix);
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-12-25 10:04:45 +08:00
|
|
|
Renderer* renderer = _director->getRenderer();
|
2022-11-28 08:37:22 +08:00
|
|
|
auto* groupCommand = renderer->getNextGroupCommand();
|
|
|
|
groupCommand->init(_globalZOrder);
|
|
|
|
renderer->addCommand(groupCommand);
|
|
|
|
renderer->pushGroup(groupCommand->getRenderQueueID());
|
2019-11-23 20:27:39 +08:00
|
|
|
|
2022-06-24 14:18:48 +08:00
|
|
|
auto beginCommand = renderer->nextCallbackCommand();
|
|
|
|
beginCommand->init(_globalZOrder);
|
2022-07-16 10:43:05 +08:00
|
|
|
beginCommand->func = AX_CALLBACK_0(RenderTexture::onBegin, this);
|
2022-06-24 14:18:48 +08:00
|
|
|
renderer->addCommand(beginCommand);
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void RenderTexture::end()
|
|
|
|
{
|
2021-12-25 10:04:45 +08:00
|
|
|
Renderer* renderer = _director->getRenderer();
|
2022-06-24 14:18:48 +08:00
|
|
|
|
|
|
|
auto endCommand = renderer->nextCallbackCommand();
|
|
|
|
endCommand->init(_globalZOrder);
|
2022-07-16 10:43:05 +08:00
|
|
|
endCommand->func = AX_CALLBACK_0(RenderTexture::onEnd, this);
|
2022-06-24 14:18:48 +08:00
|
|
|
|
|
|
|
renderer->addCommand(endCommand);
|
2019-11-23 20:27:39 +08:00
|
|
|
renderer->popGroup();
|
|
|
|
|
2020-09-13 19:16:59 +08:00
|
|
|
_director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
|
|
|
|
_director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void RenderTexture::setClearFlags(ClearFlag clearFlags)
|
|
|
|
{
|
|
|
|
_clearFlags = clearFlags;
|
2021-12-25 10:04:45 +08:00
|
|
|
if (_clearFlags != ClearFlag::NONE && !_depthStencilTexture)
|
2019-11-23 20:27:39 +08:00
|
|
|
{
|
|
|
|
_clearFlags = ClearFlag::COLOR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RenderTexture::clearColorAttachment()
|
|
|
|
{
|
2022-06-24 14:18:48 +08:00
|
|
|
auto renderer = _director->getRenderer();
|
|
|
|
auto beforeClearAttachmentCommand = renderer->nextCallbackCommand();
|
|
|
|
beforeClearAttachmentCommand->init(0);
|
2023-03-08 08:34:17 +08:00
|
|
|
beforeClearAttachmentCommand->func = [this, renderer]() -> void {
|
2020-10-16 16:25:10 +08:00
|
|
|
_oldRenderTarget = renderer->getRenderTarget();
|
|
|
|
renderer->setRenderTarget(_renderTarget);
|
|
|
|
};
|
2022-06-24 14:18:48 +08:00
|
|
|
renderer->addCommand(beforeClearAttachmentCommand);
|
2020-10-16 16:25:10 +08:00
|
|
|
|
|
|
|
Color4F color(0.f, 0.f, 0.f, 0.f);
|
|
|
|
renderer->clear(ClearFlag::COLOR, color, 1, 0, _globalZOrder);
|
|
|
|
|
2022-06-24 14:18:48 +08:00
|
|
|
// auto renderer = _director->getRenderer();
|
|
|
|
auto afterClearAttachmentCommand = renderer->nextCallbackCommand();
|
|
|
|
afterClearAttachmentCommand->init(0);
|
2023-03-08 08:34:17 +08:00
|
|
|
afterClearAttachmentCommand->func = [this, renderer]() -> void { renderer->setRenderTarget(_oldRenderTarget); };
|
2022-06-24 14:18:48 +08:00
|
|
|
renderer->addCommand(afterClearAttachmentCommand);
|
2019-11-23 20:27:39 +08:00
|
|
|
}
|
|
|
|
|
2022-07-11 17:50:21 +08:00
|
|
|
NS_AX_END
|