Fixed fading on skinned objects. Added initialiazeShapePipelines on GeometryCache to postpone simple pipeline creation after initializeGL

This commit is contained in:
Olivier Prat 2017-06-08 09:24:12 +02:00
parent de143d0ea2
commit 20d4fcbbc7
8 changed files with 151 additions and 73 deletions

View file

@ -907,6 +907,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// Make sure we don't time out during slow operations at startup
updateHeartbeat();
// Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success.
DependencyManager::get<GeometryCache>()->initializeShapePipelines();
// sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value.
// The value will be 0 if the user blew away settings this session, which is both a feature and a bug.

View file

@ -15,6 +15,7 @@
#include <DependencyManager.h>
#include <GeometryCache.h>
#include <PerfStat.h>
#include <FadeEffect.h>
#include <render-utils/simple_vert.h>
#include <render-utils/simple_frag.h>
@ -120,11 +121,14 @@ void RenderableShapeEntityItem::render(RenderArgs* args) {
DependencyManager::get<GeometryCache>()->renderShape(batch, MAPPING[_shape]);
}
} else {
// FIXME, support instanced multi-shape rendering using multidraw indirect
color.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
// FIXME, support instanced multi-shape rendering using multidraw indirect
auto geometryCache = DependencyManager::get<GeometryCache>();
auto fadeEffect = DependencyManager::get<FadeEffect>();
auto pipeline = color.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline();
assert(pipeline != nullptr);
if (render::ShapeKey(args->_globalShapeKey).isWireframe()) {
geometryCache->renderWireShapeInstance(batch, MAPPING[_shape], color, pipeline);
} else {

View file

@ -11,8 +11,8 @@
<@func transformModelToFadePos(objectTransform, objectPosition, fadePosition)@>
{
<$transformModelToWorldPos($objectTransform$, $objectPosition$, $fadePosition$)$>
<$fadePosition$> -= vec4(<$objectTransform$>._model[3].xyz, 0.f);
vec4 objectVector = vec4(<$objectPosition$>.xyz, 0.f);
<$transformModelToWorldPos($objectTransform$, objectVector, $fadePosition$)$>
}
<@endfunc@>
@ -26,6 +26,17 @@ vec2 hash2D(vec3 position) {
return position.xy* vec2(0.1677, 0.221765) + position.z*0.561;
}
float noise3D(vec3 position) {
return textureLod(fadeMaskMap, hash2D(position), 0).r;
/*const float ONE_OVER_MAX_POSITIVE_INT = (1.f / 2147483648.f);
int3 iPosition = int3(position);
int position = iPosition.x + (iPosition.y*57) + (iPosition.z*3023);
int bits = (position << 13) ^ position;
int pseudoRandomPositiveInt = (bits * ((bits*bits*15731)+789221)+1376312589) & 0x7fffffff;
float pseudoRandomFloatZeroToOne = ONE_OVER_MAX_POSITIVE_INT * (float)pseudoRandomPositiveInt;
return pseudoRandomFloatZeroToOne;*/
}
float evalFadeMask(vec3 position) {
const float FADE_MASK_INV_SCALE = 1.0;
@ -33,14 +44,14 @@ float evalFadeMask(vec3 position) {
vec3 noisePosition = position * FADE_MASK_INV_SCALE + fadeOffset;
vec3 noisePositionFloored = floor(noisePosition);
vec3 noisePositionFraction = fract(noisePosition);
float noiseLowXLowYLowZ = textureLod(fadeMaskMap, hash2D(noisePositionFloored), 0).r;
float noiseLowXHighYLowZ = textureLod(fadeMaskMap, hash2D(noisePositionFloored+vec3(0,1,0)), 0).r;
float noiseHighXLowYLowZ = textureLod(fadeMaskMap, hash2D(noisePositionFloored+vec3(1,0,0)), 0).r;
float noiseHighXHighYLowZ = textureLod(fadeMaskMap, hash2D(noisePositionFloored+vec3(1,1,0)), 0).r;
float noiseLowXLowYHighZ = textureLod(fadeMaskMap, hash2D(noisePositionFloored+vec3(0,0,1)), 0).r;
float noiseLowXHighYHighZ = textureLod(fadeMaskMap, hash2D(noisePositionFloored+vec3(0,1,1)), 0).r;
float noiseHighXLowYHighZ = textureLod(fadeMaskMap, hash2D(noisePositionFloored+vec3(1,0,1)), 0).r;
float noiseHighXHighYHighZ = textureLod(fadeMaskMap, hash2D(noisePositionFloored+vec3(1,1,1)), 0).r;
float noiseLowXLowYLowZ = noise3D(noisePositionFloored);
float noiseLowXHighYLowZ = noise3D(noisePositionFloored+vec3(0,1,0));
float noiseHighXLowYLowZ = noise3D(noisePositionFloored+vec3(1,0,0));
float noiseHighXHighYLowZ = noise3D(noisePositionFloored+vec3(1,1,0));
float noiseLowXLowYHighZ = noise3D(noisePositionFloored+vec3(0,0,1));
float noiseLowXHighYHighZ = noise3D(noisePositionFloored+vec3(0,1,1));
float noiseHighXLowYHighZ = noise3D(noisePositionFloored+vec3(1,0,1));
float noiseHighXHighYHighZ = noise3D(noisePositionFloored+vec3(1,1,1));
vec4 maskLowZ = vec4(noiseLowXLowYLowZ, noiseLowXHighYLowZ, noiseHighXLowYLowZ, noiseHighXHighYLowZ);
vec4 maskHighZ = vec4(noiseLowXLowYHighZ, noiseLowXHighYHighZ, noiseHighXLowYHighZ, noiseHighXHighYHighZ);
vec4 maskXY = mix(maskLowZ, maskHighZ, noisePositionFraction.z);

View file

@ -14,14 +14,11 @@ FadeEffect::FadeEffect() :
_fadeMaskMap = DependencyManager::get<TextureCache>()->getImageTexture(texturePath, image::TextureUsage::STRICT_TEXTURE);
}
render::ShapeKey::Builder FadeEffect::getKeyBuilder() const {
render::ShapeKey::Builder builder;
render::ShapeKey::Builder FadeEffect::getKeyBuilder(render::ShapeKey::Builder builder) const {
if (_isDebugEnabled) {
// Force fade for everyone
builder.withFade();
}
return builder;
}
@ -41,20 +38,27 @@ float FadeEffect::computeFadePercent(quint64 startTime) const {
}
void FadeEffect::bindPerItem(gpu::Batch& batch, RenderArgs* args, glm::vec3 offset, quint64 startTime, State state) const {
if (state != Complete || _isDebugEnabled) {
const gpu::ShaderPointer& program = args->_pipeline->pipeline->getProgram();
int fadeOffsetLoc = program->getUniforms().findLocation("fadeOffset");
int fadePercentLoc = program->getUniforms().findLocation("fadePercent");
float percent;
bindPerItem(batch, args->_pipeline->pipeline.get(), offset, startTime, state);
}
// A bit ugly to have the test at every bind...
if (!_isDebugEnabled) {
percent = computeFadePercent(startTime);
void FadeEffect::bindPerItem(gpu::Batch& batch, const gpu::Pipeline* pipeline, glm::vec3 offset, quint64 startTime, State state) const {
if (state != Complete || _isDebugEnabled) {
auto& program = pipeline->getProgram();
auto fadeOffsetLoc = program->getUniforms().findLocation("fadeOffset");
auto fadePercentLoc = program->getUniforms().findLocation("fadePercent");
if (fadeOffsetLoc >= 0 && fadePercentLoc >= 0) {
float percent;
// A bit ugly to have the test at every bind...
if (!_isDebugEnabled) {
percent = computeFadePercent(startTime);
}
else {
percent = _debugFadePercent;
}
batch._glUniform1f(fadePercentLoc, percent);
batch._glUniform3f(fadeOffsetLoc, offset.x, offset.y, offset.z);
}
else {
percent = _debugFadePercent;
}
batch._glUniform1f(fadePercentLoc, percent);
batch._glUniform3f(fadeOffsetLoc, offset.x, offset.y, offset.z);
}
}

View file

@ -36,10 +36,12 @@ public:
void setDebugFadePercent(float value) { assert(value >= 0.f && value <= 1.f); _debugFadePercent = value; }
float getDebugFadePercent() const { return _debugFadePercent; }
render::ShapeKey::Builder getKeyBuilder() const;
render::ShapeKey::Builder getKeyBuilder(render::ShapeKey::Builder builder = render::ShapeKey::Builder()) const;
void bindPerBatch(gpu::Batch& batch) const;
void bindPerItem(gpu::Batch& batch, RenderArgs* args, glm::vec3 offset, quint64 startTime, State state = InProgress) const;
void bindPerItem(gpu::Batch& batch, const gpu::Pipeline* pipeline, glm::vec3 offset, quint64 startTime, State state = InProgress) const;
float computeFadePercent(quint64 startTime) const;
private:

View file

@ -24,6 +24,7 @@
#include "TextureCache.h"
#include "RenderUtilsLogging.h"
#include "FadeEffect.h"
#include "gpu/StandardShaderLib.h"
@ -35,6 +36,9 @@
#include "simple_vert.h"
#include "simple_textured_frag.h"
#include "simple_textured_unlit_frag.h"
#include "simple_fade_vert.h"
#include "simple_textured_fade_frag.h"
#include "simple_textured_unlit_fade_frag.h"
#include "simple_opaque_web_browser_frag.h"
#include "simple_opaque_web_browser_overlay_frag.h"
#include "simple_transparent_web_browser_frag.h"
@ -403,30 +407,13 @@ gpu::Stream::FormatPointer& getInstancedSolidStreamFormat() {
render::ShapePipelinePointer GeometryCache::_simpleOpaquePipeline;
render::ShapePipelinePointer GeometryCache::_simpleTransparentPipeline;
render::ShapePipelinePointer GeometryCache::_simpleOpaqueFadePipeline;
render::ShapePipelinePointer GeometryCache::_simpleTransparentFadePipeline;
render::ShapePipelinePointer GeometryCache::_simpleWirePipeline;
GeometryCache::GeometryCache() :
_nextID(0) {
buildShapes();
GeometryCache::_simpleOpaquePipeline =
std::make_shared<render::ShapePipeline>(getSimplePipeline(false, false, true, false), nullptr,
[](const render::ShapePipeline&, gpu::Batch& batch) {
// Set the defaults needed for a simple program
batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO,
DependencyManager::get<TextureCache>()->getWhiteTexture());
}
);
GeometryCache::_simpleTransparentPipeline =
std::make_shared<render::ShapePipeline>(getSimplePipeline(false, true, true, false), nullptr,
[](const render::ShapePipeline&, gpu::Batch& batch) {
// Set the defaults needed for a simple program
batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO,
DependencyManager::get<TextureCache>()->getWhiteTexture());
}
);
GeometryCache::_simpleWirePipeline =
std::make_shared<render::ShapePipeline>(getSimplePipeline(false, false, true, true), nullptr,
[](const render::ShapePipeline&, gpu::Batch& batch) {});
}
GeometryCache::~GeometryCache() {
@ -471,6 +458,35 @@ void setupBatchInstance(gpu::Batch& batch, gpu::BufferPointer colorBuffer) {
batch.setInputBuffer(gpu::Stream::COLOR, colorView);
}
void GeometryCache::initializeShapePipelines() {
GeometryCache::_simpleOpaquePipeline = getShapePipeline(false, false, true, false);
GeometryCache::_simpleTransparentPipeline = getShapePipeline(false, true, true, false);
GeometryCache::_simpleOpaqueFadePipeline = getShapePipeline(false, false, true, false, false, true);
GeometryCache::_simpleTransparentFadePipeline = getShapePipeline(false, true, true, false, false, true);
GeometryCache::_simpleWirePipeline = getShapePipeline(false, false, true, true);
}
render::ShapePipelinePointer GeometryCache::getShapePipeline(bool textured, bool transparent, bool culled,
bool unlit, bool depthBias, bool fading) {
return std::make_shared<render::ShapePipeline>(getSimplePipeline(textured, transparent, culled, unlit, depthBias, fading), nullptr,
[](const render::ShapePipeline&, gpu::Batch& batch) {
// Set the defaults needed for a simple program
batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO,
DependencyManager::get<TextureCache>()->getWhiteTexture());
}
);
}
render::ShapePipelinePointer GeometryCache::getOpaqueShapePipeline(bool isFading) {
isFading = isFading || DependencyManager::get<FadeEffect>()->isDebugEnabled();
return isFading ? _simpleOpaqueFadePipeline : _simpleOpaquePipeline;
}
render::ShapePipelinePointer GeometryCache::getTransparentShapePipeline(bool isFading) {
isFading = isFading || DependencyManager::get<FadeEffect>()->isDebugEnabled();
return isFading ? _simpleTransparentFadePipeline : _simpleTransparentPipeline;
}
void GeometryCache::renderShape(gpu::Batch& batch, Shape shape) {
batch.setInputFormat(getSolidStreamFormat());
_shapes[shape].draw(batch);
@ -1714,6 +1730,7 @@ public:
IS_CULLED_FLAG,
IS_UNLIT_FLAG,
HAS_DEPTH_BIAS_FLAG,
IS_FADING_FLAG,
NUM_FLAGS,
};
@ -1724,6 +1741,7 @@ public:
IS_CULLED = (1 << IS_CULLED_FLAG),
IS_UNLIT = (1 << IS_UNLIT_FLAG),
HAS_DEPTH_BIAS = (1 << HAS_DEPTH_BIAS_FLAG),
IS_FADING = (1 << IS_FADING_FLAG),
};
typedef unsigned short Flags;
@ -1734,6 +1752,7 @@ public:
bool isCulled() const { return isFlag(IS_CULLED); }
bool isUnlit() const { return isFlag(IS_UNLIT); }
bool hasDepthBias() const { return isFlag(HAS_DEPTH_BIAS); }
bool isFading() const { return isFlag(IS_FADING); }
Flags _flags = 0;
short _spare = 0;
@ -1742,9 +1761,9 @@ public:
SimpleProgramKey(bool textured = false, bool transparent = false, bool culled = true,
bool unlit = false, bool depthBias = false) {
bool unlit = false, bool depthBias = false, bool fading = false) {
_flags = (textured ? IS_TEXTURED : 0) | (transparent ? IS_TRANSPARENT : 0) | (culled ? IS_CULLED : 0) |
(unlit ? IS_UNLIT : 0) | (depthBias ? HAS_DEPTH_BIAS : 0);
(unlit ? IS_UNLIT : 0) | (depthBias ? HAS_DEPTH_BIAS : 0) | (fading ? IS_FADING : 0);
}
SimpleProgramKey(int bitmask) : _flags(bitmask) {}
@ -1818,23 +1837,8 @@ void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool tra
}
}
gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transparent, bool culled, bool unlit, bool depthBiased) {
SimpleProgramKey config { textured, transparent, culled, unlit, depthBiased };
// Compile the shaders
static std::once_flag once;
std::call_once(once, [&]() {
auto VS = gpu::Shader::createVertex(std::string(simple_vert));
auto PS = gpu::Shader::createPixel(std::string(simple_textured_frag));
auto PSUnlit = gpu::Shader::createPixel(std::string(simple_textured_unlit_frag));
_simpleShader = gpu::Shader::createProgram(VS, PS);
_unlitShader = gpu::Shader::createProgram(VS, PSUnlit);
gpu::Shader::BindingSet slotBindings;
gpu::Shader::makeProgram(*_simpleShader, slotBindings);
gpu::Shader::makeProgram(*_unlitShader, slotBindings);
});
gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transparent, bool culled, bool unlit, bool depthBiased, bool fading) {
SimpleProgramKey config { textured, transparent, culled, unlit, depthBiased, fading };
// If the pipeline already exists, return it
auto it = _simplePrograms.find(config);
@ -1842,6 +1846,37 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp
return it.value();
}
// Compile the shaders
if (!fading) {
static std::once_flag once;
std::call_once(once, [&]() {
auto VS = gpu::Shader::createVertex(std::string(simple_vert));
auto PS = gpu::Shader::createPixel(std::string(simple_textured_frag));
auto PSUnlit = gpu::Shader::createPixel(std::string(simple_textured_unlit_frag));
_simpleShader = gpu::Shader::createProgram(VS, PS);
_unlitShader = gpu::Shader::createProgram(VS, PSUnlit);
gpu::Shader::BindingSet slotBindings;
gpu::Shader::makeProgram(*_simpleShader, slotBindings);
gpu::Shader::makeProgram(*_unlitShader, slotBindings);
});
} else {
static std::once_flag once;
std::call_once(once, [&]() {
auto VS = gpu::Shader::createVertex(std::string(simple_fade_vert));
auto PS = gpu::Shader::createPixel(std::string(simple_textured_fade_frag));
auto PSUnlit = gpu::Shader::createPixel(std::string(simple_textured_unlit_fade_frag));
_simpleFadeShader = gpu::Shader::createProgram(VS, PS);
_unlitFadeShader = gpu::Shader::createProgram(VS, PSUnlit);
gpu::Shader::BindingSet slotBindings;
gpu::Shader::makeProgram(*_simpleFadeShader, slotBindings);
gpu::Shader::makeProgram(*_unlitFadeShader, slotBindings);
});
}
// If the pipeline did not exist, make it
auto state = std::make_shared<gpu::State>();
if (config.isCulled()) {
@ -1858,7 +1893,7 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
gpu::ShaderPointer program = (config.isUnlit()) ? _unlitShader : _simpleShader;
gpu::ShaderPointer program = (config.isUnlit()) ? (config.isFading() ? _unlitFadeShader : _unlitShader) : (config.isFading() ? _simpleFadeShader : _simpleShader);
gpu::PipelinePointer pipeline = gpu::Pipeline::create(program, state);
_simplePrograms.insert(config, pipeline);
return pipeline;
@ -1900,19 +1935,23 @@ void renderInstances(gpu::Batch& batch, const glm::vec4& color, bool isWire,
}
void GeometryCache::renderSolidShapeInstance(gpu::Batch& batch, GeometryCache::Shape shape, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) {
assert(pipeline != nullptr);
renderInstances(batch, color, false, pipeline, shape);
}
void GeometryCache::renderWireShapeInstance(gpu::Batch& batch, GeometryCache::Shape shape, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) {
assert(pipeline != nullptr);
renderInstances(batch, color, true, pipeline, shape);
}
void GeometryCache::renderSolidSphereInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) {
assert(pipeline != nullptr);
renderInstances(batch, color, false, pipeline, GeometryCache::Sphere);
}
void GeometryCache::renderWireSphereInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) {
assert(pipeline != nullptr);
renderInstances(batch, color, true, pipeline, GeometryCache::Sphere);
}
@ -1921,6 +1960,7 @@ void GeometryCache::renderWireSphereInstance(gpu::Batch& batch, const glm::vec4&
//#define DEBUG_SHAPES
void GeometryCache::renderSolidCubeInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) {
assert(pipeline != nullptr);
#ifdef DEBUG_SHAPES
static auto startTime = usecTimestampNow();
renderInstances(INSTANCE_NAME, batch, color, pipeline, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
@ -1960,5 +2000,6 @@ void GeometryCache::renderSolidCubeInstance(gpu::Batch& batch, const glm::vec4&
void GeometryCache::renderWireCubeInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) {
static const std::string INSTANCE_NAME = __FUNCTION__;
assert(pipeline != nullptr);
renderInstances(batch, color, true, pipeline, GeometryCache::Cube);
}

View file

@ -156,7 +156,7 @@ public:
bool unlit = false, bool depthBias = false);
// Get the pipeline to render static geometry
gpu::PipelinePointer getSimplePipeline(bool textured = false, bool transparent = false, bool culled = true,
bool unlit = false, bool depthBias = false);
bool unlit = false, bool depthBias = false, bool fading = false);
void bindOpaqueWebBrowserProgram(gpu::Batch& batch, bool isAA);
gpu::PipelinePointer getOpaqueWebBrowserProgram(bool isAA);
@ -164,9 +164,19 @@ public:
void bindTransparentWebBrowserProgram(gpu::Batch& batch, bool isAA);
gpu::PipelinePointer getTransparentWebBrowserProgram(bool isAA);
render::ShapePipelinePointer getOpaqueShapePipeline() { return GeometryCache::_simpleOpaquePipeline; }
render::ShapePipelinePointer getTransparentShapePipeline() { return GeometryCache::_simpleTransparentPipeline; }
render::ShapePipelinePointer getWireShapePipeline() { return GeometryCache::_simpleWirePipeline; }
void initializeShapePipelines();
render::ShapePipelinePointer getShapePipeline(bool textured = false, bool transparent = false, bool culled = true,
bool unlit = false, bool depthBias = false, bool fading = false);
render::ShapePipelinePointer getOpaqueShapePipeline() { assert(_simpleOpaquePipeline != nullptr); return _simpleOpaquePipeline; }
render::ShapePipelinePointer getTransparentShapePipeline() { assert(_simpleTransparentPipeline != nullptr); return _simpleTransparentPipeline; }
render::ShapePipelinePointer getOpaqueFadeShapePipeline() { assert(_simpleOpaqueFadePipeline != nullptr); return _simpleOpaqueFadePipeline; }
render::ShapePipelinePointer getTransparentFadeShapePipeline() { assert(_simpleTransparentFadePipeline != nullptr); return _simpleTransparentFadePipeline; }
render::ShapePipelinePointer getOpaqueShapePipeline(bool isFading);
render::ShapePipelinePointer getTransparentShapePipeline(bool isFading);
render::ShapePipelinePointer getWireShapePipeline() { assert(_simpleWirePipeline != nullptr); return GeometryCache::_simpleWirePipeline; }
// Static (instanced) geometry
void renderShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer);
@ -418,8 +428,12 @@ private:
gpu::ShaderPointer _simpleShader;
gpu::ShaderPointer _unlitShader;
gpu::ShaderPointer _simpleFadeShader;
gpu::ShaderPointer _unlitFadeShader;
static render::ShapePipelinePointer _simpleOpaquePipeline;
static render::ShapePipelinePointer _simpleTransparentPipeline;
static render::ShapePipelinePointer _simpleOpaqueFadePipeline;
static render::ShapePipelinePointer _simpleTransparentFadePipeline;
static render::ShapePipelinePointer _simpleOpaqueOverlayPipeline;
static render::ShapePipelinePointer _simpleTransparentOverlayPipeline;
static render::ShapePipelinePointer _simpleWirePipeline;

View file

@ -50,6 +50,6 @@ void main(void) {
TransformCamera cam = getTransformCamera();
TransformObject obj = getTransformObject();
<$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$>
<$transformModelToFadePos(obj, inPosition, _worldFadePosition)$>
<$transformModelToFadePos(obj, position, _worldFadePosition)$>
<$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, _normal.xyz)$>
}