aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/gpu/GrBackendTextureImageGenerator.cpp
blob: c7e77d65a552e073b206920b619c107635dd1269 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/*
 * Copyright 2017 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "GrBackendTextureImageGenerator.h"

#include "GrContext.h"
#include "GrContextPriv.h"
#include "GrGpu.h"
#include "GrResourceCache.h"
#include "GrResourceProvider.h"
#include "GrSemaphore.h"
#include "GrTexture.h"

#include "SkGr.h"
#include "SkMessageBus.h"

GrBackendTextureImageGenerator::RefHelper::~RefHelper() {
    SkASSERT(nullptr == fBorrowedTexture);
    SkASSERT(SK_InvalidGenID == fBorrowingContextID);

    // Generator has been freed, and no one is borrowing the texture. Notify the original cache
    // that it can free the last ref, so it happens on the correct thread.
    GrGpuResourceFreedMessage msg { fOriginalTexture, fOwningContextID };
    SkMessageBus<GrGpuResourceFreedMessage>::Post(msg);
}

// TODO: I copied this from SkImage_Gpu, perhaps we put a version of this somewhere else?
static GrBackendTexture make_backend_texture_from_handle(GrBackend backend,
                                                         int width, int height,
                                                         GrPixelConfig config,
                                                         GrBackendObject handle) {
#if SK_VULKAN
    if (kVulkan_GrBackend == backend) {
        GrVkImageInfo* vkInfo = (GrVkImageInfo*)(handle);
        return GrBackendTexture(width, height, *vkInfo);
    }
#endif
    SkASSERT(kOpenGL_GrBackend == backend);
    GrGLTextureInfo* glInfo = (GrGLTextureInfo*)(handle);
    return GrBackendTexture(width, height, config, *glInfo);
}

std::unique_ptr<SkImageGenerator>
GrBackendTextureImageGenerator::Make(sk_sp<GrTexture> texture, sk_sp<GrSemaphore> semaphore,
                                     SkAlphaType alphaType, sk_sp<SkColorSpace> colorSpace) {
    if (colorSpace && (!colorSpace->gammaCloseToSRGB() && !colorSpace->gammaIsLinear())) {
        return nullptr;
    }

    SkColorType colorType = kUnknown_SkColorType;
    if (!GrPixelConfigToColorType(texture->config(), &colorType)) {
        return nullptr;
    }

    GrContext* context = texture->getContext();

    // Attach our texture to this context's resource cache. This ensures that deletion will happen
    // in the correct thread/context. This adds the only ref to the texture that will persist from
    // this point. That ref will be released when the generator's RefHelper is freed.
    context->getResourceCache()->insertCrossContextGpuResource(texture.get());

    GrBackend backend = context->contextPriv().getBackend();
    GrBackendTexture backendTexture = make_backend_texture_from_handle(backend,
                                                                       texture->width(),
                                                                       texture->height(),
                                                                       texture->config(),
                                                                       texture->getTextureHandle());

    SkImageInfo info = SkImageInfo::Make(texture->width(), texture->height(), colorType, alphaType,
                                         std::move(colorSpace));
    return std::unique_ptr<SkImageGenerator>(new GrBackendTextureImageGenerator(
            info, texture.get(), context->uniqueID(), std::move(semaphore), backendTexture));
}

GrBackendTextureImageGenerator::GrBackendTextureImageGenerator(const SkImageInfo& info,
                                                               GrTexture* texture,
                                                               uint32_t owningContextID,
                                                               sk_sp<GrSemaphore> semaphore,
                                                               const GrBackendTexture& backendTex)
    : INHERITED(info)
    , fRefHelper(new RefHelper(texture, owningContextID))
    , fSemaphore(std::move(semaphore))
    , fLastBorrowingContextID(SK_InvalidGenID)
    , fBackendTexture(backendTex)
    , fSurfaceOrigin(texture->origin()) { }

GrBackendTextureImageGenerator::~GrBackendTextureImageGenerator() {
    fRefHelper->unref();
}

///////////////////////////////////////////////////////////////////////////////////////////////////

#if SK_SUPPORT_GPU
void GrBackendTextureImageGenerator::ReleaseRefHelper_TextureReleaseProc(void* ctx) {
    RefHelper* refHelper = static_cast<RefHelper*>(ctx);
    SkASSERT(refHelper);

    // Release texture so another context can use it
    refHelper->fBorrowedTexture = nullptr;
    refHelper->fBorrowingContextID = SK_InvalidGenID;
    refHelper->unref();
}

sk_sp<GrTextureProxy> GrBackendTextureImageGenerator::onGenerateTexture(
        GrContext* context, const SkImageInfo& info, const SkIPoint& origin) {
    SkASSERT(context);

    if (context->contextPriv().getBackend() != fBackendTexture.backend()) {
        return nullptr;
    }

    sk_sp<GrTexture> tex;

    if (fRefHelper->fBorrowingContextID == context->uniqueID()) {
        // If a client re-draws the same image multiple times, the texture we return will be cached
        // and re-used. If they draw a subset, though, we may be re-called. In that case, we want
        // to re-use the borrowed texture we've previously created.
        tex = sk_ref_sp(fRefHelper->fBorrowedTexture);
        SkASSERT(tex);
    } else {
        // The texture is available or borrwed by another context. Try for exclusive access.
        uint32_t expectedID = SK_InvalidGenID;
        if (!fRefHelper->fBorrowingContextID.compare_exchange(&expectedID, context->uniqueID())) {
            // Some other context is currently borrowing the texture. We aren't allowed to use it.
            return nullptr;
        } else {
            // Wait on a semaphore when a new context has just started borrowing the texture. This
            // is conservative, but shouldn't be too expensive.
            if (fSemaphore && fLastBorrowingContextID != context->uniqueID()) {
                context->getGpu()->waitSemaphore(fSemaphore);
                fLastBorrowingContextID = context->uniqueID();
            }
        }

        // We just gained access to the texture. If we're on the original context, we could use the
        // original texture, but we'd have no way of detecting that it's no longer in-use. So we
        // always make a wrapped copy, where the release proc informs us that the context is done
        // with it. This is unfortunate - we'll have two texture objects referencing the same GPU
        // object. However, no client can ever see the original texture, so this should be safe.
        tex = context->resourceProvider()->wrapBackendTexture(fBackendTexture, fSurfaceOrigin,
                                                              kNone_GrBackendTextureFlag, 0,
                                                              kBorrow_GrWrapOwnership);
        if (!tex) {
            fRefHelper->fBorrowingContextID = SK_InvalidGenID;
            return nullptr;
        }
        fRefHelper->fBorrowedTexture = tex.get();

        tex->setRelease(ReleaseRefHelper_TextureReleaseProc, fRefHelper);
        fRefHelper->ref();
    }

    SkASSERT(fRefHelper->fBorrowingContextID == context->uniqueID());

    sk_sp<GrTextureProxy> proxy = GrSurfaceProxy::MakeWrapped(std::move(tex));

    if (0 == origin.fX && 0 == origin.fY &&
        info.width() == fBackendTexture.width() && info.height() == fBackendTexture.height()) {
        // If the caller wants the entire texture, we're done
        return proxy;
    } else {
        // Otherwise, make a copy of the requested subset. Make sure our temporary is renderable,
        // because Vulkan will want to do the copy as a draw.
        GrSurfaceDesc desc;
        desc.fConfig = proxy->config();
        desc.fWidth = info.width();
        desc.fHeight = info.height();
        desc.fOrigin = proxy->origin();
        desc.fIsMipMapped = proxy->isMipMapped();
        desc.fFlags = kRenderTarget_GrSurfaceFlag;

        sk_sp<GrSurfaceContext> sContext(context->contextPriv().makeDeferredSurfaceContext(
            desc, SkBackingFit::kExact, SkBudgeted::kYes));
        if (!sContext) {
            return nullptr;
        }

        SkIRect subset = SkIRect::MakeXYWH(origin.fX, origin.fY, info.width(), info.height());
        if (!sContext->copy(proxy.get(), subset, SkIPoint::Make(0, 0))) {
            return nullptr;
        }

        return sContext->asTextureProxyRef();
    }
}
#endif