1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 * Copyright (C) 2012-2014, The Linux Foundation. All rights reserved.
4 *
5 * Not a Contribution, Apache license notifications and license are
6 * retained for attribution purposes only.
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21#define DEBUG_FBUPDATE 0
22#include <cutils/properties.h>
23#include <gralloc_priv.h>
24#include <overlay.h>
25#include <overlayRotator.h>
26#include "hwc_fbupdate.h"
27#include "mdp_version.h"
28
29using namespace qdutils;
30using namespace overlay;
31using overlay::Rotator;
32using namespace overlay::utils;
33
34namespace qhwc {
35
36namespace ovutils = overlay::utils;
37
38IFBUpdate* IFBUpdate::getObject(hwc_context_t *ctx, const int& dpy) {
39    if(qdutils::MDPVersion::getInstance().isSrcSplit()) {
40        return new FBSrcSplit(ctx, dpy);
41    } else if(isDisplaySplit(ctx, dpy)) {
42        return new FBUpdateSplit(ctx, dpy);
43    }
44    return new FBUpdateNonSplit(ctx, dpy);
45}
46
47IFBUpdate::IFBUpdate(hwc_context_t *ctx, const int& dpy) : mDpy(dpy) {
48    unsigned int size = 0;
49    uint32_t xres = ctx->dpyAttr[mDpy].xres;
50    uint32_t yres = ctx->dpyAttr[mDpy].yres;
51    if (ctx->dpyAttr[dpy].customFBSize) {
52        //GPU will render and compose at new resolution
53        //So need to have FB at new resolution
54        xres = ctx->dpyAttr[mDpy].xres_new;
55        yres = ctx->dpyAttr[mDpy].yres_new;
56    }
57    getBufferAttributes((int)xres, (int)yres,
58            ctx->dpyAttr[mDpy].fbformat,
59            0,
60            mAlignedFBWidth,
61            mAlignedFBHeight,
62            mTileEnabled, size);
63}
64
65void IFBUpdate::reset() {
66    mModeOn = false;
67    mRot = NULL;
68}
69
70bool IFBUpdate::prepareAndValidate(hwc_context_t *ctx,
71            hwc_display_contents_1 *list, int fbZorder) {
72    hwc_layer_1_t *layer = &list->hwLayers[list->numHwLayers - 1];
73    mModeOn = prepare(ctx, list, layer->displayFrame, fbZorder) &&
74            ctx->mOverlay->validateAndSet(mDpy, ctx->dpyAttr[mDpy].fd);
75    return mModeOn;
76}
77
78//================= Low res====================================
79FBUpdateNonSplit::FBUpdateNonSplit(hwc_context_t *ctx, const int& dpy):
80        IFBUpdate(ctx, dpy) {}
81
82void FBUpdateNonSplit::reset() {
83    IFBUpdate::reset();
84    mDest = ovutils::OV_INVALID;
85}
86
87bool FBUpdateNonSplit::preRotateExtDisplay(hwc_context_t *ctx,
88                                            hwc_layer_1_t *layer,
89                                            ovutils::Whf &info,
90                                            hwc_rect_t& sourceCrop,
91                                            ovutils::eMdpFlags& mdpFlags,
92                                            int& rotFlags)
93{
94    int extOrient = getExtOrientation(ctx);
95    ovutils::eTransform orient = static_cast<ovutils::eTransform >(extOrient);
96    if(mDpy && (extOrient & HWC_TRANSFORM_ROT_90)) {
97        mRot = ctx->mRotMgr->getNext();
98        if(mRot == NULL) return false;
99        ctx->mLayerRotMap[mDpy]->add(layer, mRot);
100        // Composed FB content will have black bars, if the viewFrame of the
101        // external is different from {0, 0, fbWidth, fbHeight}, so intersect
102        // viewFrame with sourceCrop to avoid those black bars
103        sourceCrop = getIntersection(sourceCrop, ctx->mViewFrame[mDpy]);
104        //Configure rotator for pre-rotation
105        if(configRotator(mRot, info, sourceCrop, mdpFlags, orient, 0) < 0) {
106            ALOGE("%s: configRotator Failed!", __FUNCTION__);
107            mRot = NULL;
108            return false;
109        }
110        updateSource(orient, info, sourceCrop, mRot);
111        rotFlags |= ovutils::ROT_PREROTATED;
112    }
113    return true;
114}
115
116bool FBUpdateNonSplit::prepare(hwc_context_t *ctx, hwc_display_contents_1 *list,
117                             hwc_rect_t fbUpdatingRect, int fbZorder) {
118    if(!ctx->mMDP.hasOverlay) {
119        ALOGD_IF(DEBUG_FBUPDATE, "%s, this hw doesnt support overlays",
120                 __FUNCTION__);
121        return false;
122    }
123    mModeOn = configure(ctx, list, fbUpdatingRect, fbZorder);
124    return mModeOn;
125}
126
127// Configure
128bool FBUpdateNonSplit::configure(hwc_context_t *ctx, hwc_display_contents_1 *list,
129                               hwc_rect_t fbUpdatingRect, int fbZorder) {
130    bool ret = false;
131    hwc_layer_1_t *layer = &list->hwLayers[list->numHwLayers - 1];
132    if (LIKELY(ctx->mOverlay)) {
133        overlay::Overlay& ov = *(ctx->mOverlay);
134
135        ovutils::Whf info(mAlignedFBWidth, mAlignedFBHeight,
136                ovutils::getMdpFormat(ctx->dpyAttr[mDpy].fbformat,
137                    mTileEnabled));
138
139        Overlay::PipeSpecs pipeSpecs;
140        pipeSpecs.formatClass = Overlay::FORMAT_RGB;
141        pipeSpecs.needsScaling = qhwc::needsScaling(layer);
142        pipeSpecs.dpy = mDpy;
143        pipeSpecs.mixer = Overlay::MIXER_DEFAULT;
144        pipeSpecs.fb = true;
145
146        ovutils::eDest dest = ov.getPipe(pipeSpecs);
147        if(dest == ovutils::OV_INVALID) { //None available
148            ALOGE("%s: No pipes available to configure fb for dpy %d",
149                __FUNCTION__, mDpy);
150            return false;
151        }
152        mDest = dest;
153
154        if((mDpy && ctx->deviceOrientation) &&
155            ctx->listStats[mDpy].isDisplayAnimating) {
156            fbZorder = 0;
157        }
158
159        ovutils::eMdpFlags mdpFlags = ovutils::OV_MDP_BLEND_FG_PREMULT;
160        ovutils::eZorder zOrder = static_cast<ovutils::eZorder>(fbZorder);
161
162        hwc_rect_t sourceCrop = integerizeSourceCrop(layer->sourceCropf);
163        hwc_rect_t displayFrame = layer->displayFrame;
164
165        // No FB update optimization on (1) Custom FB resolution,
166        // (2) External Mirror mode, (3) External orientation
167        if(!ctx->dpyAttr[mDpy].customFBSize && !ctx->mBufferMirrorMode
168           && !ctx->mExtOrientation) {
169            sourceCrop = fbUpdatingRect;
170            displayFrame = fbUpdatingRect;
171        }
172
173        int transform = layer->transform;
174        int rotFlags = ovutils::ROT_FLAGS_NONE;
175
176        ovutils::eTransform orient =
177                    static_cast<ovutils::eTransform>(transform);
178        // use ext orientation if any
179        int extOrient = getExtOrientation(ctx);
180
181        // Do not use getNonWormholeRegion() function to calculate the
182        // sourceCrop during animation on external display and
183        // Dont do wormhole calculation when extorientation is set on External
184        // Dont do wormhole calculation when scaling mode is set on External
185        if(ctx->listStats[mDpy].isDisplayAnimating && mDpy) {
186            sourceCrop = layer->displayFrame;
187        } else if((mDpy && !extOrient
188                  && !ctx->dpyAttr[mDpy].mMDPScalingMode)) {
189            if(ctx->mOverlay->isUIScalingOnExternalSupported() &&
190                !ctx->dpyAttr[mDpy].customFBSize) {
191                getNonWormholeRegion(list, sourceCrop);
192                displayFrame = sourceCrop;
193            }
194        }
195        calcExtDisplayPosition(ctx, NULL, mDpy, sourceCrop, displayFrame,
196                                   transform, orient);
197        //Store the displayFrame, will be used in getDisplayViewFrame
198        ctx->dpyAttr[mDpy].mDstRect = displayFrame;
199        setMdpFlags(ctx, layer, mdpFlags, 0, transform);
200        // For External use rotator if there is a rotation value set
201        ret = preRotateExtDisplay(ctx, layer, info,
202                sourceCrop, mdpFlags, rotFlags);
203        if(!ret) {
204            ALOGE("%s: preRotate for external Failed!", __FUNCTION__);
205            return false;
206        }
207        //For the mdp, since either we are pre-rotating or MDP does flips
208        orient = ovutils::OVERLAY_TRANSFORM_0;
209        transform = 0;
210        ovutils::PipeArgs parg(mdpFlags, info, zOrder,
211                               static_cast<ovutils::eRotFlags>(rotFlags),
212                               ovutils::DEFAULT_PLANE_ALPHA,
213                               (ovutils::eBlending)
214                               getBlending(layer->blending));
215        ret = true;
216        if(configMdp(ctx->mOverlay, parg, orient, sourceCrop, displayFrame,
217                    NULL, mDest) < 0) {
218            ALOGE("%s: configMdp failed for dpy %d", __FUNCTION__, mDpy);
219            ret = false;
220        }
221    }
222    return ret;
223}
224
225bool FBUpdateNonSplit::draw(hwc_context_t *ctx, private_handle_t *hnd)
226{
227    if(!mModeOn) {
228        return true;
229    }
230    bool ret = true;
231    overlay::Overlay& ov = *(ctx->mOverlay);
232    ovutils::eDest dest = mDest;
233    int fd = hnd->fd;
234    uint32_t offset = (uint32_t)hnd->offset;
235    if(mRot) {
236        if(!mRot->queueBuffer(fd, offset))
237            return false;
238        fd = mRot->getDstMemId();
239        offset = mRot->getDstOffset();
240    }
241    if (!ov.queueBuffer(fd, offset, dest)) {
242        ALOGE("%s: queueBuffer failed for FBUpdate", __FUNCTION__);
243        ret = false;
244    }
245    return ret;
246}
247
248//================= High res====================================
249FBUpdateSplit::FBUpdateSplit(hwc_context_t *ctx, const int& dpy):
250        IFBUpdate(ctx, dpy) {}
251
252void FBUpdateSplit::reset() {
253    IFBUpdate::reset();
254    mDestLeft = ovutils::OV_INVALID;
255    mDestRight = ovutils::OV_INVALID;
256    mRot = NULL;
257}
258
259bool FBUpdateSplit::prepare(hwc_context_t *ctx, hwc_display_contents_1 *list,
260                              hwc_rect_t fbUpdatingRect, int fbZorder) {
261    if(!ctx->mMDP.hasOverlay) {
262        ALOGD_IF(DEBUG_FBUPDATE, "%s, this hw doesnt support overlays",
263                 __FUNCTION__);
264        return false;
265    }
266    mModeOn = configure(ctx, list, fbUpdatingRect, fbZorder);
267    ALOGD_IF(DEBUG_FBUPDATE, "%s, mModeOn = %d", __FUNCTION__, mModeOn);
268    return mModeOn;
269}
270
271// Configure
272bool FBUpdateSplit::configure(hwc_context_t *ctx,
273        hwc_display_contents_1 *list, hwc_rect_t fbUpdatingRect, int fbZorder) {
274    bool ret = false;
275    hwc_layer_1_t *layer = &list->hwLayers[list->numHwLayers - 1];
276    if (LIKELY(ctx->mOverlay)) {
277        ovutils::Whf info(mAlignedFBWidth, mAlignedFBHeight,
278                          ovutils::getMdpFormat(HAL_PIXEL_FORMAT_RGBA_8888,
279                                                mTileEnabled));
280
281        overlay::Overlay& ov = *(ctx->mOverlay);
282        ovutils::eMdpFlags mdpFlags = ovutils::OV_MDP_BLEND_FG_PREMULT;
283        ovutils::eZorder zOrder = static_cast<ovutils::eZorder>(fbZorder);
284        ovutils::eTransform orient =
285            static_cast<ovutils::eTransform>(layer->transform);
286        const int hw_w = ctx->dpyAttr[mDpy].xres;
287        const int hw_h = ctx->dpyAttr[mDpy].yres;
288        const int lSplit = getLeftSplit(ctx, mDpy);
289        mDestLeft = ovutils::OV_INVALID;
290        mDestRight = ovutils::OV_INVALID;
291        hwc_rect_t sourceCrop = integerizeSourceCrop(layer->sourceCropf);
292        hwc_rect_t displayFrame = layer->displayFrame;
293
294        // No FB update optimization on (1) Custom FB resolution,
295        // (2) External Mirror mode, (3) External orientation
296        if(!ctx->dpyAttr[mDpy].customFBSize && !ctx->mBufferMirrorMode
297           && !ctx->mExtOrientation) {
298            sourceCrop = fbUpdatingRect;
299            displayFrame = fbUpdatingRect;
300        }
301
302        int transform = layer->transform;
303        // use ext orientation if any
304        int extOrient = getExtOrientation(ctx);
305
306        // Do not use getNonWormholeRegion() function to calculate the
307        // sourceCrop during animation on external display and
308        // Dont do wormhole calculation when extorientation is set on External
309        // Dont do wormhole calculation when scaling mode is set on External
310        if(ctx->listStats[mDpy].isDisplayAnimating && mDpy) {
311            sourceCrop = layer->displayFrame;
312        } else if((mDpy && !extOrient
313                  && !ctx->dpyAttr[mDpy].mMDPScalingMode)) {
314            if(!qdutils::MDPVersion::getInstance().is8x26() &&
315                !ctx->dpyAttr[mDpy].customFBSize) {
316                getNonWormholeRegion(list, sourceCrop);
317                displayFrame = sourceCrop;
318            }
319        }
320
321        calcExtDisplayPosition(ctx, NULL, mDpy, sourceCrop, displayFrame,
322                                   transform, orient);
323
324        ret = true;
325        Overlay::PipeSpecs pipeSpecs;
326        pipeSpecs.formatClass = Overlay::FORMAT_RGB;
327        pipeSpecs.needsScaling = qhwc::needsScaling(layer);
328        pipeSpecs.dpy = mDpy;
329        pipeSpecs.fb = true;
330
331        /* Configure left pipe */
332        if(displayFrame.left < lSplit) {
333            pipeSpecs.mixer = Overlay::MIXER_LEFT;
334            ovutils::eDest destL = ov.getPipe(pipeSpecs);
335            if(destL == ovutils::OV_INVALID) { //None available
336                ALOGE("%s: No pipes available to configure fb for dpy %d's left"
337                      " mixer", __FUNCTION__, mDpy);
338                return false;
339            }
340
341            mDestLeft = destL;
342
343            //XXX: FB layer plane alpha is currently sent as zero from
344            //surfaceflinger
345            ovutils::PipeArgs pargL(mdpFlags,
346                                    info,
347                                    zOrder,
348                                    ovutils::ROT_FLAGS_NONE,
349                                    ovutils::DEFAULT_PLANE_ALPHA,
350                                    (ovutils::eBlending)
351                                    getBlending(layer->blending));
352            hwc_rect_t cropL = sourceCrop;
353            hwc_rect_t dstL = displayFrame;
354            hwc_rect_t scissorL = {0, 0, lSplit, hw_h };
355            qhwc::calculate_crop_rects(cropL, dstL, scissorL, 0);
356
357            if (configMdp(ctx->mOverlay, pargL, orient, cropL,
358                           dstL, NULL, destL)< 0) {
359                ALOGE("%s: configMdp fails for left FB", __FUNCTION__);
360                ret = false;
361            }
362        }
363
364        /* Configure right pipe */
365        if(displayFrame.right > lSplit) {
366            pipeSpecs.mixer = Overlay::MIXER_RIGHT;
367            ovutils::eDest destR = ov.getPipe(pipeSpecs);
368            if(destR == ovutils::OV_INVALID) { //None available
369                ALOGE("%s: No pipes available to configure fb for dpy %d's"
370                      " right mixer", __FUNCTION__, mDpy);
371                return false;
372            }
373
374            mDestRight = destR;
375            ovutils::eMdpFlags mdpFlagsR = mdpFlags;
376            ovutils::setMdpFlags(mdpFlagsR, ovutils::OV_MDSS_MDP_RIGHT_MIXER);
377
378            //XXX: FB layer plane alpha is currently sent as zero from
379            //surfaceflinger
380            ovutils::PipeArgs pargR(mdpFlagsR,
381                                    info,
382                                    zOrder,
383                                    ovutils::ROT_FLAGS_NONE,
384                                    ovutils::DEFAULT_PLANE_ALPHA,
385                                    (ovutils::eBlending)
386                                    getBlending(layer->blending));
387
388            hwc_rect_t cropR = sourceCrop;
389            hwc_rect_t dstR = displayFrame;
390            hwc_rect_t scissorR = {lSplit, 0, hw_w, hw_h };
391            qhwc::calculate_crop_rects(cropR, dstR, scissorR, 0);
392
393            dstR.left -= lSplit;
394            dstR.right -= lSplit;
395
396            if (configMdp(ctx->mOverlay, pargR, orient, cropR,
397                           dstR, NULL, destR) < 0) {
398                ALOGE("%s: configMdp fails for right FB", __FUNCTION__);
399                ret = false;
400            }
401        }
402    }
403    return ret;
404}
405
406bool FBUpdateSplit::draw(hwc_context_t *ctx, private_handle_t *hnd)
407{
408    if(!mModeOn) {
409        return true;
410    }
411    bool ret = true;
412    overlay::Overlay& ov = *(ctx->mOverlay);
413    if(mDestLeft != ovutils::OV_INVALID) {
414        if (!ov.queueBuffer(hnd->fd, (uint32_t)hnd->offset, mDestLeft)) {
415            ALOGE("%s: queue failed for left of dpy = %d",
416                  __FUNCTION__, mDpy);
417            ret = false;
418        }
419    }
420    if(mDestRight != ovutils::OV_INVALID) {
421        if (!ov.queueBuffer(hnd->fd, (uint32_t)hnd->offset, mDestRight)) {
422            ALOGE("%s: queue failed for right of dpy = %d",
423                  __FUNCTION__, mDpy);
424            ret = false;
425        }
426    }
427    return ret;
428}
429
430//=================FBSrcSplit====================================
431FBSrcSplit::FBSrcSplit(hwc_context_t *ctx, const int& dpy):
432        FBUpdateSplit(ctx, dpy) {}
433
434bool FBSrcSplit::configure(hwc_context_t *ctx, hwc_display_contents_1 *list,
435        hwc_rect_t fbUpdatingRect, int fbZorder) {
436    hwc_layer_1_t *layer = &list->hwLayers[list->numHwLayers - 1];
437    overlay::Overlay& ov = *(ctx->mOverlay);
438
439    ovutils::Whf info(mAlignedFBWidth,
440            mAlignedFBHeight,
441            ovutils::getMdpFormat(HAL_PIXEL_FORMAT_RGBA_8888,
442                mTileEnabled));
443
444    ovutils::eMdpFlags mdpFlags = OV_MDP_BLEND_FG_PREMULT;
445    ovutils::eZorder zOrder = static_cast<ovutils::eZorder>(fbZorder);
446
447    ovutils::PipeArgs parg(mdpFlags,
448            info,
449            zOrder,
450            ovutils::ROT_FLAGS_NONE,
451            ovutils::DEFAULT_PLANE_ALPHA,
452            (ovutils::eBlending)
453            getBlending(layer->blending));
454
455    hwc_rect_t sourceCrop = integerizeSourceCrop(layer->sourceCropf);
456    hwc_rect_t displayFrame = layer->displayFrame;
457
458    // No FB update optimization on (1) Custom FB resolution,
459    // (2) External Mirror mode, (3) External orientation
460    if(!ctx->dpyAttr[mDpy].customFBSize && !ctx->mBufferMirrorMode
461       && !ctx->mExtOrientation) {
462        sourceCrop = fbUpdatingRect;
463        displayFrame = fbUpdatingRect;
464    }
465    int transform = layer->transform;
466    ovutils::eTransform orient =
467            static_cast<ovutils::eTransform>(transform);
468
469    // use ext orientation if any
470    int extOrient = getExtOrientation(ctx);
471
472    // Do not use getNonWormholeRegion() function to calculate the
473    // sourceCrop during animation on external display and
474    // Dont do wormhole calculation when extorientation is set on External
475    // Dont do wormhole calculation when scaling mode is set on External
476    if(ctx->listStats[mDpy].isDisplayAnimating && mDpy) {
477        sourceCrop = layer->displayFrame;
478    } else if((mDpy && !extOrient
479              && !ctx->dpyAttr[mDpy].mMDPScalingMode)) {
480        if(!qdutils::MDPVersion::getInstance().is8x26() &&
481            !ctx->dpyAttr[mDpy].customFBSize) {
482            getNonWormholeRegion(list, sourceCrop);
483            displayFrame = sourceCrop;
484        }
485    }
486
487    calcExtDisplayPosition(ctx, NULL, mDpy, sourceCrop, displayFrame,
488                               transform, orient);
489    hwc_rect_t cropL = sourceCrop;
490    hwc_rect_t cropR = sourceCrop;
491    hwc_rect_t dstL = displayFrame;
492    hwc_rect_t dstR = displayFrame;
493
494    //Request left pipe (or 1 by default)
495    Overlay::PipeSpecs pipeSpecs;
496    pipeSpecs.formatClass = Overlay::FORMAT_RGB;
497    pipeSpecs.needsScaling = qhwc::needsScaling(layer);
498    pipeSpecs.dpy = mDpy;
499    pipeSpecs.mixer = Overlay::MIXER_DEFAULT;
500    pipeSpecs.fb = true;
501    ovutils::eDest destL = ov.getPipe(pipeSpecs);
502    if(destL == ovutils::OV_INVALID) {
503        ALOGE("%s: No pipes available to configure fb for dpy %d's left"
504                " mixer", __FUNCTION__, mDpy);
505        return false;
506    }
507
508    ovutils::eDest destR = ovutils::OV_INVALID;
509
510    /*  Use 2 pipes IF
511        a) FB's width is > Mixer width or
512        b) On primary, driver has indicated with caps to split always. This is
513           based on an empirically derived value of panel height.
514    */
515
516    const bool primarySplitAlways = (mDpy == HWC_DISPLAY_PRIMARY) and
517            qdutils::MDPVersion::getInstance().isSrcSplitAlways();
518    const uint32_t lSplit = getLeftSplit(ctx, mDpy);
519    const uint32_t cropWidth = sourceCrop.right - sourceCrop.left;
520
521    if((cropWidth > qdutils::MDPVersion::getInstance().getMaxMixerWidth()) or
522            (primarySplitAlways and cropWidth > lSplit)) {
523        destR = ov.getPipe(pipeSpecs);
524        if(destR == ovutils::OV_INVALID) {
525            ALOGE("%s: No pipes available to configure fb for dpy %d's right"
526                    " mixer", __FUNCTION__, mDpy);
527            return false;
528        }
529
530        if(ctx->mOverlay->comparePipePriority(destL, destR) == -1) {
531            qhwc::swap(destL, destR);
532        }
533
534        //Split crop equally when using 2 pipes
535        cropL.right = (sourceCrop.right + sourceCrop.left) / 2;
536        cropR.left = cropL.right;
537        dstL.right = (displayFrame.right + displayFrame.left) / 2;
538        dstR.left = dstL.right;
539    }
540
541    mDestLeft = destL;
542    mDestRight = destR;
543
544    if(destL != OV_INVALID) {
545        if(configMdp(ctx->mOverlay, parg, orient,
546                    cropL, dstL, NULL /*metadata*/, destL) < 0) {
547            ALOGE("%s: commit failed for left mixer config", __FUNCTION__);
548            return false;
549        }
550    }
551
552    //configure right pipe
553    if(destR != OV_INVALID) {
554        if(configMdp(ctx->mOverlay, parg, orient,
555                    cropR, dstR, NULL /*metadata*/, destR) < 0) {
556            ALOGE("%s: commit failed for right mixer config", __FUNCTION__);
557            return false;
558        }
559    }
560
561    return true;
562}
563
564//---------------------------------------------------------------------
565}; //namespace qhwc
566