180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru/*
380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru * Copyright 2011 Google Inc.
480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru *
580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru * Use of this source code is governed by a BSD-style license that can be
680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru * found in the LICENSE file.
780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru */
880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru#ifndef GrTexture_DEFINED
1080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru#define GrTexture_DEFINED
1180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
1280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru#include "GrSurface.h"
137839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger#include "SkPoint.h"
147839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger#include "GrRenderTarget.h"
1580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
1680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queruclass GrResourceKey;
1780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queruclass GrTextureParams;
1880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
1980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queruclass GrTexture : public GrSurface {
2080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
2180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Querupublic:
2280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    SK_DECLARE_INST_COUNT(GrTexture)
2380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    // from GrResource
2480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    /**
2580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     * Informational texture flags
2680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     */
2780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    enum FlagBits {
2880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru        kFirstBit = (kLastPublic_GrTextureFlagBit << 1),
2980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
3080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru        /**
3180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru         * This texture should be returned to the texture cache when
3280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru         * it is no longer reffed
3380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru         */
3480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru        kReturnToCache_FlagBit        = kFirstBit,
3580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    };
3680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
3780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    void setFlag(GrTextureFlags flags) {
3880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru        fDesc.fFlags = fDesc.fFlags | flags;
3980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    }
4080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    void resetFlag(GrTextureFlags flags) {
4180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru        fDesc.fFlags = fDesc.fFlags & ~flags;
4280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    }
4380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    bool isSetFlag(GrTextureFlags flags) const {
4480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru        return 0 != (fDesc.fFlags & flags);
4580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    }
4680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
47e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger    void dirtyMipMaps(bool mipMapsDirty) {
48e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger        fMipMapsDirty = mipMapsDirty;
49e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger    }
50e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger
51e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger    bool mipMapsAreDirty() const {
52e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger        return fMipMapsDirty;
53e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger    }
54e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger
5580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    /**
5680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     *  Approximate number of bytes used by the texture
5780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     */
5880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    virtual size_t sizeInBytes() const SK_OVERRIDE {
5980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru        return (size_t) fDesc.fWidth *
6080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru                        fDesc.fHeight *
6180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru                        GrBytesPerPixel(fDesc.fConfig);
6280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    }
6380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
6480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    // GrSurface overrides
6580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    virtual bool readPixels(int left, int top, int width, int height,
6680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru                            GrPixelConfig config,
6780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru                            void* buffer,
6880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru                            size_t rowBytes = 0,
6980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru                            uint32_t pixelOpsFlags = 0) SK_OVERRIDE;
7080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
7180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    virtual void writePixels(int left, int top, int width, int height,
7280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru                             GrPixelConfig config,
7380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru                             const void* buffer,
7480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru                             size_t rowBytes = 0,
7580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru                             uint32_t pixelOpsFlags = 0) SK_OVERRIDE;
7680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
7780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    /**
7880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     * @return this texture
7980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     */
8080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    virtual GrTexture* asTexture() SK_OVERRIDE { return this; }
8180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    virtual const GrTexture* asTexture() const SK_OVERRIDE { return this; }
8280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
8380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    /**
8480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     * Retrieves the render target underlying this texture that can be passed to
8580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     * GrGpu::setRenderTarget().
8680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     *
8780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     * @return    handle to render target or NULL if the texture is not a
8880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     *            render target
8980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     */
9080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    virtual GrRenderTarget* asRenderTarget() SK_OVERRIDE {
917839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger        return fRenderTarget.get();
9280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    }
9380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    virtual const GrRenderTarget* asRenderTarget() const SK_OVERRIDE {
947839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger        return fRenderTarget.get();
9580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    }
9680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
9780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    // GrTexture
9880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    /**
9980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     * Convert from texels to normalized texture coords for POT textures
10080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     * only.
10180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     */
10280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    GrFixed normalizeFixedX(GrFixed x) const {
1030a657bbc2c6fc9daf699942e023050536d5ec95fDerek Sollenberger        SkASSERT(GrIsPow2(fDesc.fWidth));
10480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru        return x >> fShiftFixedX;
10580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    }
10680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    GrFixed normalizeFixedY(GrFixed y) const {
1070a657bbc2c6fc9daf699942e023050536d5ec95fDerek Sollenberger        SkASSERT(GrIsPow2(fDesc.fHeight));
10880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru        return y >> fShiftFixedY;
10980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    }
11080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
11180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    /**
11280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     *  Return the native ID or handle to the texture, depending on the
113363e546ed626b6dbbc42f5db87b3594bc0b5944bDerek Sollenberger     *  platform. e.g. on OpenGL, return the texture ID.
11480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     */
115363e546ed626b6dbbc42f5db87b3594bc0b5944bDerek Sollenberger    virtual GrBackendObject getTextureHandle() const = 0;
11680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
11780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    /**
11880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     *  Call this when the state of the native API texture object is
11980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     *  altered directly, without being tracked by skia.
12080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru     */
12180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    virtual void invalidateCachedState() = 0;
12280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
1230a657bbc2c6fc9daf699942e023050536d5ec95fDerek Sollenberger#ifdef SK_DEBUG
12480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    void validate() const {
12580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru        this->INHERITED::validate();
12680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
12780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru        this->validateDesc();
12880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    }
12980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru#endif
1300a657bbc2c6fc9daf699942e023050536d5ec95fDerek Sollenberger
13180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    static GrResourceKey ComputeKey(const GrGpu* gpu,
132d686ac77c2c485c4a3302eda9c1de597a6f8c568Derek Sollenberger                                    const GrTextureParams* params,
13380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru                                    const GrTextureDesc& desc,
134d686ac77c2c485c4a3302eda9c1de597a6f8c568Derek Sollenberger                                    const GrCacheID& cacheID);
135d686ac77c2c485c4a3302eda9c1de597a6f8c568Derek Sollenberger    static GrResourceKey ComputeScratchKey(const GrTextureDesc& desc);
13680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    static bool NeedsResizing(const GrResourceKey& key);
13758190644c30e1c4aa8e527f3503c58f841e0fcf3Derek Sollenberger    static bool NeedsBilerp(const GrResourceKey& key);
13880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
13980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queruprotected:
1407839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    // A texture refs its rt representation but not vice-versa. It is up to
1417839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    // the subclass constructor to initialize this pointer.
1427839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    SkAutoTUnref<GrRenderTarget> fRenderTarget;
14380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
144096defe64d408e54474fe19f418c95bf1a554fc7Derek Sollenberger    GrTexture(GrGpu* gpu, bool isWrapped, const GrTextureDesc& desc)
145096defe64d408e54474fe19f418c95bf1a554fc7Derek Sollenberger    : INHERITED(gpu, isWrapped, desc)
146e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger    , fRenderTarget(NULL)
147e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger    , fMipMapsDirty(true) {
14880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
14980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru        // only make sense if alloc size is pow2
150363e546ed626b6dbbc42f5db87b3594bc0b5944bDerek Sollenberger        fShiftFixedX = 31 - SkCLZ(fDesc.fWidth);
151363e546ed626b6dbbc42f5db87b3594bc0b5944bDerek Sollenberger        fShiftFixedY = 31 - SkCLZ(fDesc.fHeight);
15280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    }
1537839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    virtual ~GrTexture();
15480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
15580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    // GrResource overrides
15680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    virtual void onRelease() SK_OVERRIDE;
15780bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    virtual void onAbandon() SK_OVERRIDE;
15880bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
15980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    void validateDesc() const;
16080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
16180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queruprivate:
16280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    // these two shift a fixed-point value into normalized coordinates
16380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    // for this texture if the texture is power of two sized.
16480bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    int                 fShiftFixedX;
16580bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    int                 fShiftFixedY;
16680bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
167e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger    bool                fMipMapsDirty;
168e27eefc4844477cee5d32f51ab45ff62020cdb36Derek Sollenberger
16980bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    virtual void internal_dispose() const SK_OVERRIDE;
17080bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
17180bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru    typedef GrSurface INHERITED;
17280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru};
17380bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru
1747839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger/**
1757839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger * Represents a texture that is intended to be accessed in device coords with an offset.
1767839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger */
1777839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenbergerclass GrDeviceCoordTexture {
1787839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenbergerpublic:
1797839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    GrDeviceCoordTexture() { fOffset.set(0, 0); }
1807839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger
1817839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    GrDeviceCoordTexture(const GrDeviceCoordTexture& other) {
1827839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger        *this = other;
1837839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    }
1847839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger
1857839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    GrDeviceCoordTexture(GrTexture* texture, const SkIPoint& offset)
1867839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger        : fTexture(SkSafeRef(texture))
1877839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger        , fOffset(offset) {
1887839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    }
1897839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger
1907839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    GrDeviceCoordTexture& operator=(const GrDeviceCoordTexture& other) {
1917839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger        fTexture.reset(SkSafeRef(other.fTexture.get()));
1927839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger        fOffset = other.fOffset;
1937839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger        return *this;
1947839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    }
1957839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger
1967839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    const SkIPoint& offset() const { return fOffset; }
1977839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger
1987839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    void setOffset(const SkIPoint& offset) { fOffset = offset; }
1997839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    void setOffset(int ox, int oy) { fOffset.set(ox, oy); }
2007839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger
2017839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    GrTexture* texture() const { return fTexture.get(); }
2027839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger
2037839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    GrTexture* setTexture(GrTexture* texture) {
2047839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger        fTexture.reset(SkSafeRef(texture));
2057839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger        return texture;
2067839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    }
2077839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenbergerprivate:
2087839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    SkAutoTUnref<GrTexture> fTexture;
2097839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger    SkIPoint                fOffset;
2107839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger};
2117839ce1af63bf12fe7b3caa866970bbbb3afb13dDerek Sollenberger
21280bacfeb4bda06541e8695bd502229727bccfeaJean-Baptiste Queru#endif
213