1 /*
2 * Copyright 2022 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "tests/Test.h"
9
10 #include "include/core/SkBitmap.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkImage.h"
13 #include "include/core/SkSurface.h"
14 #include "include/gpu/graphite/Context.h"
15 #include "include/gpu/graphite/Recorder.h"
16 #include "include/gpu/graphite/Recording.h"
17 #include "src/core/SkCanvasPriv.h"
18 #include "src/gpu/graphite/Device.h"
19 #include "src/gpu/graphite/RecorderPriv.h"
20 #include "src/gpu/graphite/Resource.h"
21 #include "src/gpu/graphite/ResourceCache.h"
22 #include "src/gpu/graphite/ResourceProvider.h"
23 #include "src/gpu/graphite/SharedContext.h"
24 #include "src/gpu/graphite/Texture.h"
25 #include "src/gpu/graphite/TextureProxyView.h"
26 #include "src/image/SkImage_Base.h"
27
28 namespace skgpu::graphite {
29
30 class TestResource : public Resource {
31 public:
Make(const SharedContext * sharedContext,Ownership owned,skgpu::Budgeted budgeted,Shareable shareable)32 static sk_sp<TestResource> Make(const SharedContext* sharedContext,
33 Ownership owned,
34 skgpu::Budgeted budgeted,
35 Shareable shareable) {
36 auto resource = sk_sp<TestResource>(new TestResource(sharedContext, owned, budgeted));
37 if (!resource) {
38 return nullptr;
39 }
40
41 GraphiteResourceKey key;
42 CreateKey(&key, shareable);
43
44 resource->setKey(key);
45 return resource;
46 }
47
CreateKey(GraphiteResourceKey * key,Shareable shareable)48 static void CreateKey(GraphiteResourceKey* key, Shareable shareable) {
49 // Internally we assert that we don't make the same key twice where the only difference is
50 // shareable vs non-shareable. That allows us to now have Shareable be part of the Key's
51 // key. So here we make two different resource types so the keys will be different.
52 static const ResourceType kType = GraphiteResourceKey::GenerateResourceType();
53 static const ResourceType kShareableType = GraphiteResourceKey::GenerateResourceType();
54 ResourceType type = shareable == Shareable::kNo ? kType : kShareableType;
55 GraphiteResourceKey::Builder(key, type, 0, shareable);
56 }
57
58 private:
TestResource(const SharedContext * sharedContext,Ownership owned,skgpu::Budgeted budgeted)59 TestResource(const SharedContext* sharedContext, Ownership owned, skgpu::Budgeted budgeted)
60 : Resource(sharedContext, owned, budgeted) {}
61
freeGpuData()62 void freeGpuData() override {}
63 };
64
create_image_data(const SkImageInfo & info)65 static sk_sp<SkData> create_image_data(const SkImageInfo& info) {
66 const size_t rowBytes = info.minRowBytes();
67 sk_sp<SkData> data(SkData::MakeUninitialized(rowBytes * info.height()));
68 {
69 SkBitmap bm;
70 bm.installPixels(info, data->writable_data(), rowBytes);
71 SkCanvas canvas(bm);
72 canvas.clear(SK_ColorRED);
73 }
74 return data;
75 }
76
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphiteBudgetedResourcesTest,reporter,context)77 DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(GraphiteBudgetedResourcesTest, reporter, context) {
78 std::unique_ptr<Recorder> recorder = context->makeRecorder();
79 ResourceProvider* resourceProvider = recorder->priv().resourceProvider();
80 ResourceCache* resourceCache = resourceProvider->resourceCache();
81 const SharedContext* sharedContext = resourceProvider->sharedContext();
82
83 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 0);
84 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 0);
85
86 // Test making a non budgeted, non shareable resource.
87 auto resource = TestResource::Make(
88 sharedContext, Ownership::kOwned, skgpu::Budgeted::kNo, Shareable::kNo);
89 if (!resource) {
90 ERRORF(reporter, "Failed to make TestResource");
91 return;
92 }
93 Resource* resourcePtr = resource.get();
94
95 REPORTER_ASSERT(reporter, resource->budgeted() == skgpu::Budgeted::kNo);
96 resourceCache->insertResource(resourcePtr);
97 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
98 // Resource is not shareable and we have a ref on it. Thus it shouldn't ben findable in the
99 // cache.
100 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 0);
101
102 // When we reset our TestResource it should go back into the cache since it can be used as a
103 // scratch texture (since it is not shareable). At that point the budget should be changed to
104 // skgpu::Budgeted::kYes.
105 resource.reset();
106 resourceCache->forceProcessReturnedResources();
107 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
108 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 1);
109 // Even though we reset our ref on the resource we still have the ptr to it and should be the
110 // resource in the cache. So in general this is dangerous it should be safe for this test to
111 // directly access the texture.
112 REPORTER_ASSERT(reporter, resourcePtr->budgeted() == skgpu::Budgeted::kYes);
113
114 GraphiteResourceKey key;
115 TestResource::CreateKey(&key, Shareable::kNo);
116 Resource* resourcePtr2 = resourceCache->findAndRefResource(key, skgpu::Budgeted::kNo);
117 REPORTER_ASSERT(reporter, resourcePtr == resourcePtr2);
118 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 1);
119 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 0);
120 REPORTER_ASSERT(reporter, resourcePtr2->budgeted() == skgpu::Budgeted::kNo);
121 resourcePtr2->unref();
122 resourceCache->forceProcessReturnedResources();
123
124 // Test making a non budgeted, non shareable resource.
125 resource = TestResource::Make(
126 sharedContext, Ownership::kOwned, skgpu::Budgeted::kYes, Shareable::kYes);
127 if (!resource) {
128 ERRORF(reporter, "Failed to make TestResource");
129 return;
130 }
131 resourcePtr = resource.get();
132 REPORTER_ASSERT(reporter, resource->budgeted() == skgpu::Budgeted::kYes);
133 resourceCache->insertResource(resourcePtr);
134 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2);
135 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2);
136
137 resource.reset();
138 resourceCache->forceProcessReturnedResources();
139 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2);
140 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2);
141 REPORTER_ASSERT(reporter, resourcePtr->budgeted() == skgpu::Budgeted::kYes);
142
143 TestResource::CreateKey(&key, Shareable::kYes);
144 resourcePtr2 = resourceCache->findAndRefResource(key, skgpu::Budgeted::kYes);
145 REPORTER_ASSERT(reporter, resourcePtr == resourcePtr2);
146 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 2);
147 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2);
148 REPORTER_ASSERT(reporter, resourcePtr2->budgeted() == skgpu::Budgeted::kYes);
149 resourcePtr2->unref();
150
151 ///////////////////////////////////////////////////////////////////////////////////////////////
152 // Test that SkImage's and SkSurface's underlying Resource's follow the expected budgeted
153 // system.
154 auto info = SkImageInfo::Make(10, 10, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
155
156 // First test SkImages. Since we can't directly create a Graphite SkImage we first have to make
157 // a raster SkImage than convert that to a Graphite SkImage via makeTextureImage.
158 sk_sp<SkData> data(create_image_data(info));
159 sk_sp<SkImage> image = SkImage::MakeRasterData(info, std::move(data), info.minRowBytes());
160 REPORTER_ASSERT(reporter, image);
161
162 sk_sp<SkImage> imageGpu = image->makeTextureImage(recorder.get());
163 REPORTER_ASSERT(reporter, imageGpu);
164
165 TextureProxy* imageProxy = nullptr;
166 {
167 // We don't want the view holding a ref to the Proxy or else we can't send things back to
168 // the cache.
169 auto [view, colorType] = as_IB(imageGpu.get())->asView(recorder.get(), Mipmapped::kNo);
170 REPORTER_ASSERT(reporter, view);
171 imageProxy = view.proxy();
172 }
173 // Make sure the proxy is instantiated
174 if (!imageProxy->instantiate(resourceProvider)) {
175 ERRORF(reporter, "Failed to instantiate Proxy");
176 return;
177 }
178 const Resource* imageResourcePtr = imageProxy->texture();
179 REPORTER_ASSERT(reporter, imageResourcePtr);
180 // There is an extra resource for the buffer that is uploading the data to the texture
181 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 4);
182 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 2);
183 REPORTER_ASSERT(reporter, imageResourcePtr->budgeted() == skgpu::Budgeted::kNo);
184
185 // Submit all upload work so we can drop refs to the image and get it returned to the cache.
186 std::unique_ptr<Recording> recording = recorder->snap();
187 if (!recording) {
188 ERRORF(reporter, "Failed to make recording");
189 return;
190 }
191 InsertRecordingInfo insertInfo;
192 insertInfo.fRecording = recording.get();
193 context->insertRecording(insertInfo);
194 context->submit(SyncToCpu::kYes);
195 recording.reset();
196 imageGpu.reset();
197 resourceCache->forceProcessReturnedResources();
198
199 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 4);
200 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 4);
201 REPORTER_ASSERT(reporter, imageResourcePtr->budgeted() == skgpu::Budgeted::kYes);
202
203 // Now try an SkSurface. This is simpler since we can directly create Graphite SkSurface's.
204 sk_sp<SkSurface> surface = SkSurface::MakeGraphite(recorder.get(), info);
205 if (!surface) {
206 ERRORF(reporter, "Failed to make surface");
207 return;
208 }
209
210 TextureProxy* surfaceProxy = SkCanvasPriv::TopDeviceGraphiteTargetProxy(surface->getCanvas());
211 if (!surfaceProxy) {
212 ERRORF(reporter, "Failed to get surface proxy");
213 return;
214 }
215
216 // Make sure the proxy is instantiated
217 if (!surfaceProxy->instantiate(resourceProvider)) {
218 ERRORF(reporter, "Failed to instantiate surface proxy");
219 return;
220 }
221 const Resource* surfaceResourcePtr = surfaceProxy->texture();
222
223 REPORTER_ASSERT(reporter, resourceCache->getResourceCount() == 5);
224 REPORTER_ASSERT(reporter, resourceCache->numFindableResources() == 4);
225 REPORTER_ASSERT(reporter, surfaceResourcePtr->budgeted() == skgpu::Budgeted::kNo);
226
227 // The creation of the surface may have added an initial clear to it. Thus if we just reset the
228 // surface it will flush the clean on the device and we don't be dropping all our refs to the
229 // surface. So we force all the work to happen first.
230 recording = recorder->snap();
231 insertInfo.fRecording = recording.get();
232 context->insertRecording(insertInfo);
233 context->submit(SyncToCpu::kYes);
234 recording.reset();
235
236 surface.reset();
237 resourceCache->forceProcessReturnedResources();
238 REPORTER_ASSERT(reporter, surfaceResourcePtr->budgeted() == skgpu::Budgeted::kYes);
239 }
240
241 } // namespace skgpu::graphite
242