Flutter Engine
The Flutter Engine
SkImage_Ganesh.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2012 Google Inc.
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
9
13#include "include/core/SkRect.h"
14#include "include/core/SkSize.h"
20#include "include/gpu/GrTypes.h"
36#include "src/gpu/ganesh/SkGr.h"
42
43#include <cstddef>
44#include <utility>
45
46class SkMatrix;
47enum SkColorType : int;
48enum class SkTileMode;
49
50inline SkImage_Ganesh::ProxyChooser::ProxyChooser(sk_sp<GrSurfaceProxy> stableProxy)
51 : fStableProxy(std::move(stableProxy)) {
52 SkASSERT(fStableProxy);
53}
54
55inline SkImage_Ganesh::ProxyChooser::ProxyChooser(sk_sp<GrSurfaceProxy> stableProxy,
56 sk_sp<GrSurfaceProxy> volatileProxy,
57 sk_sp<GrRenderTask> copyTask,
58 int volatileProxyTargetCount)
59 : fStableProxy(std::move(stableProxy))
60 , fVolatileProxy(std::move(volatileProxy))
61 , fVolatileToStableCopyTask(std::move(copyTask))
62 , fVolatileProxyTargetCount(volatileProxyTargetCount) {
63 SkASSERT(fStableProxy);
64 SkASSERT(fVolatileProxy);
65 SkASSERT(fVolatileToStableCopyTask);
66}
67
68inline SkImage_Ganesh::ProxyChooser::~ProxyChooser() {
69 // The image is being destroyed. If there is a stable copy proxy but we've been able to use
70 // the volatile proxy for all requests then we can skip the copy.
71 if (fVolatileToStableCopyTask) {
72 fVolatileToStableCopyTask->makeSkippable();
73 }
74}
75
76inline sk_sp<GrSurfaceProxy> SkImage_Ganesh::ProxyChooser::chooseProxy(
77 GrRecordingContext* context) {
78 SkAutoSpinlock hold(fLock);
79 if (fVolatileProxy) {
80 SkASSERT(fVolatileProxyTargetCount <= fVolatileProxy->getTaskTargetCount());
81 // If this image is used off the direct context it originated on, i.e. on a recording-only
82 // context, we don't know how the recording context's actions are ordered WRT direct context
83 // actions until the recording context's DAG is imported into the direct context.
84 if (context->asDirectContext() &&
85 fVolatileProxyTargetCount == fVolatileProxy->getTaskTargetCount()) {
86 return fVolatileProxy;
87 }
88 fVolatileProxy.reset();
89 fVolatileToStableCopyTask.reset();
90 return fStableProxy;
91 }
92 return fStableProxy;
93}
94
95inline sk_sp<GrSurfaceProxy> SkImage_Ganesh::ProxyChooser::switchToStableProxy() {
96 SkAutoSpinlock hold(fLock);
97 fVolatileProxy.reset();
98 fVolatileToStableCopyTask.reset();
99 return fStableProxy;
100}
101
102inline sk_sp<GrSurfaceProxy> SkImage_Ganesh::ProxyChooser::makeVolatileProxyStable() {
103 SkAutoSpinlock hold(fLock);
104 if (fVolatileProxy) {
105 fStableProxy = std::move(fVolatileProxy);
106 fVolatileToStableCopyTask->makeSkippable();
107 fVolatileToStableCopyTask.reset();
108 }
109 return fStableProxy;
110}
111
112inline bool SkImage_Ganesh::ProxyChooser::surfaceMustCopyOnWrite(
113 GrSurfaceProxy* surfaceProxy) const {
114 SkAutoSpinlock hold(fLock);
115 return surfaceProxy->underlyingUniqueID() == fStableProxy->underlyingUniqueID();
116}
117
118inline size_t SkImage_Ganesh::ProxyChooser::gpuMemorySize() const {
119 SkAutoSpinlock hold(fLock);
120 size_t size = fStableProxy->gpuMemorySize();
121 if (fVolatileProxy) {
122 SkASSERT(fVolatileProxy->gpuMemorySize() == size);
123 }
124 return size;
125}
126
127inline skgpu::Mipmapped SkImage_Ganesh::ProxyChooser::mipmapped() const {
128 SkAutoSpinlock hold(fLock);
129 skgpu::Mipmapped mipmapped = fStableProxy->asTextureProxy()->mipmapped();
130 if (fVolatileProxy) {
131 SkASSERT(fVolatileProxy->asTextureProxy()->mipmapped() == mipmapped);
132 }
133 return mipmapped;
134}
135
136inline skgpu::Protected SkImage_Ganesh::ProxyChooser::isProtected() const {
137 SkAutoSpinlock hold(fLock);
138 skgpu::Protected isProtected = fStableProxy->asTextureProxy()->isProtected();
139 if (fVolatileProxy) {
140 SkASSERT(fVolatileProxy->asTextureProxy()->isProtected() == isProtected);
141 }
142 return isProtected;
143}
144
145#ifdef SK_DEBUG
146inline const GrBackendFormat& SkImage_Ganesh::ProxyChooser::backendFormat() {
147 SkAutoSpinlock hold(fLock);
148 if (fVolatileProxy) {
149 SkASSERT(fVolatileProxy->backendFormat() == fStableProxy->backendFormat());
150 }
151 return fStableProxy->backendFormat();
152}
153#endif
154
155//////////////////////////////////////////////////////////////////////////////
156
158 uint32_t uniqueID,
161 : INHERITED(std::move(context),
162 SkImageInfo::Make(view.proxy()->backingStoreDimensions(), std::move(info)),
163 uniqueID)
164 , fChooser(view.detachProxy())
165 , fSwizzle(view.swizzle())
166 , fOrigin(view.origin()) {
167#ifdef SK_DEBUG
168 const GrBackendFormat& format = fChooser.backendFormat();
169 const GrCaps* caps = this->context()->priv().caps();
173#endif
174}
175
177 GrSurfaceProxyView volatileSrc,
178 sk_sp<GrSurfaceProxy> stableCopy,
179 sk_sp<GrRenderTask> copyTask,
180 int volatileSrcTargetCount,
182 : INHERITED(
183 std::move(dContext),
184 SkImageInfo::Make(volatileSrc.proxy()->backingStoreDimensions(), std::move(info)),
186 , fChooser(std::move(stableCopy),
187 volatileSrc.detachProxy(),
188 std::move(copyTask),
189 volatileSrcTargetCount)
190 , fSwizzle(volatileSrc.swizzle())
191 , fOrigin(volatileSrc.origin()) {
192#ifdef SK_DEBUG
193 const GrBackendFormat& format = fChooser.backendFormat();
194 const GrCaps* caps = this->context()->priv().caps();
198#endif
199}
200
202 GrSurfaceProxyView volatileSrc,
203 SkColorInfo colorInfo) {
204 SkASSERT(rContext);
205 SkASSERT(volatileSrc);
206 SkASSERT(volatileSrc.proxy()->asTextureProxy());
207 skgpu::Mipmapped mm = volatileSrc.proxy()->asTextureProxy()->mipmapped();
208 sk_sp<GrRenderTask> copyTask;
209 auto copy = GrSurfaceProxy::Copy(rContext.get(),
210 volatileSrc.refProxy(),
211 volatileSrc.origin(),
212 mm,
215 /*label=*/"ImageGpu_MakeWithVolatileSrc",
216 &copyTask);
217 if (!copy) {
218 return nullptr;
219 }
220 // We only attempt to make a dual-proxy image on a direct context. This optimziation requires
221 // knowing how things are ordered and recording-only contexts are not well ordered WRT other
222 // recording contexts.
223 if (auto direct = sk_ref_sp(rContext->asDirectContext())) {
224 int targetCount = volatileSrc.proxy()->getTaskTargetCount();
225 return sk_sp<SkImage>(new SkImage_Ganesh(std::move(direct),
226 std::move(volatileSrc),
227 std::move(copy),
228 std::move(copyTask),
229 targetCount,
230 std::move(colorInfo)));
231 }
232 GrSurfaceProxyView copyView(std::move(copy), volatileSrc.origin(), volatileSrc.swizzle());
233 return sk_make_sp<SkImage_Ganesh>(
234 std::move(rContext), kNeedNewImageUniqueID, std::move(copyView), std::move(colorInfo));
235}
236
238
240 return fChooser.surfaceMustCopyOnWrite(surfaceProxy);
241}
242
243bool SkImage_Ganesh::onHasMipmaps() const { return fChooser.mipmapped() == skgpu::Mipmapped::kYes; }
244
246 return fChooser.isProtected() == skgpu::Protected::kYes;
247}
248
250 const GrFlushInfo& info) const {
251 if (!fContext->priv().matches(dContext) || dContext->abandoned()) {
252 if (info.fSubmittedProc) {
253 info.fSubmittedProc(info.fSubmittedContext, false);
254 }
255 if (info.fFinishedProc) {
256 info.fFinishedProc(info.fFinishedContext);
257 }
259 }
260
261 sk_sp<GrSurfaceProxy> proxy = fChooser.chooseProxy(dContext);
262 return dContext->priv().flushSurface(
264}
265
267 bool flushPendingGrContextIO,
268 GrSurfaceOrigin* origin) const {
269 auto direct = fContext->asDirectContext();
270 if (!direct) {
271 // This image was created with a DDL context and cannot be instantiated.
272 return false;
273 }
274 if (direct->abandoned()) {
275 return false;
276 }
277
278 // We don't know how client's use of the texture will be ordered WRT Skia's. Ensure the
279 // texture seen by the client won't be mutated by a SkSurface.
280 sk_sp<GrSurfaceProxy> proxy = fChooser.switchToStableProxy();
281
282 if (!proxy->isInstantiated()) {
283 auto resourceProvider = direct->priv().resourceProvider();
284
285 if (!proxy->instantiate(resourceProvider)) {
286 return false;
287 }
288 }
289
290 GrTexture* texture = proxy->peekTexture();
291 if (!texture) {
292 return false;
293 }
294 if (flushPendingGrContextIO) {
295 direct->priv().flushSurface(proxy.get());
296 }
297 if (origin) {
298 *origin = fOrigin;
299 }
300 if (outTexture) {
301 *outTexture = texture->getBackendTexture();
302 }
303 return true;
304}
305
306size_t SkImage_Ganesh::textureSize() const { return fChooser.gpuMemorySize(); }
307
309 sk_sp<SkColorSpace> targetCS,
310 GrDirectContext* dContext) const {
311 SkColorInfo info(targetCT, this->alphaType(), std::move(targetCS));
312 if (!fContext->priv().matches(dContext)) {
313 return nullptr;
314 }
315
316 sk_sp<GrSurfaceProxy> proxy = fChooser.chooseProxy(dContext);
317
318 auto sfc = dContext->priv().makeSFCWithFallback(GrImageInfo(info, this->dimensions()),
320 /* sampleCount= */ 1,
322 proxy->isProtected());
323 if (!sfc) {
324 return nullptr;
325 }
326 // We respecify info's CT because we called MakeWithFallback.
327 auto ct = GrColorTypeToSkColorType(sfc->colorInfo().colorType());
328 info = info.makeColorType(ct);
329
330 // Draw this image's texture into the SFC.
331 auto [view, _] = skgpu::ganesh::AsView(dContext, this, skgpu::Mipmapped(this->hasMipmaps()));
332 auto texFP = GrTextureEffect::Make(std::move(view), this->alphaType());
333 auto colorFP =
334 GrColorSpaceXformEffect::Make(std::move(texFP), this->imageInfo().colorInfo(), info);
335 sfc->fillWithFP(std::move(colorFP));
336
337 return sk_make_sp<SkImage_Ganesh>(
338 sk_ref_sp(dContext), kNeedNewImageUniqueID, sfc->readSurfaceView(), std::move(info));
339}
340
342 // It doesn't seem worth the complexity of trying to share the ProxyChooser among multiple
343 // images. Just fall back to the stable copy.
344 GrSurfaceProxyView view(fChooser.switchToStableProxy(), fOrigin, fSwizzle);
345 return sk_make_sp<SkImage_Ganesh>(
346 fContext,
348 std::move(view),
349 this->imageInfo().colorInfo().makeColorSpace(std::move(newCS)));
350}
351
353 SkIRect srcRect,
354 RescaleGamma rescaleGamma,
355 RescaleMode rescaleMode,
357 ReadPixelsContext context) const {
358 auto dContext = fContext->asDirectContext();
359 if (!dContext) {
360 // DDL TODO: buffer up the readback so it occurs when the DDL is drawn?
361 callback(context, nullptr);
362 return;
363 }
364 auto ctx = dContext->priv().makeSC(this->makeView(dContext), this->imageInfo().colorInfo());
365 if (!ctx) {
366 callback(context, nullptr);
367 return;
368 }
369 ctx->asyncRescaleAndReadPixels(
370 dContext, info, srcRect, rescaleGamma, rescaleMode, callback, context);
371}
372
374 bool readAlpha,
375 sk_sp<SkColorSpace> dstColorSpace,
376 SkIRect srcRect,
377 SkISize dstSize,
378 RescaleGamma rescaleGamma,
379 RescaleMode rescaleMode,
381 ReadPixelsContext context) const {
382 auto dContext = fContext->asDirectContext();
383 if (!dContext) {
384 // DDL TODO: buffer up the readback so it occurs when the DDL is drawn?
385 callback(context, nullptr);
386 return;
387 }
388 auto ctx = dContext->priv().makeSC(this->makeView(dContext), this->imageInfo().colorInfo());
389 if (!ctx) {
390 callback(context, nullptr);
391 return;
392 }
393 ctx->asyncRescaleAndReadPixelsYUV420(dContext,
394 yuvColorSpace,
395 readAlpha,
396 std::move(dstColorSpace),
397 srcRect,
398 dstSize,
399 rescaleGamma,
400 rescaleMode,
401 callback,
402 context);
403}
404
405void SkImage_Ganesh::generatingSurfaceIsDeleted() { fChooser.makeVolatileProxyStable(); }
406
407std::tuple<GrSurfaceProxyView, GrColorType> SkImage_Ganesh::asView(
408 GrRecordingContext* recordingContext,
409 skgpu::Mipmapped mipmapped,
411 if (!fContext->priv().matches(recordingContext)) {
412 return {};
413 }
415 return {skgpu::ganesh::CopyView(recordingContext,
416 this->makeView(recordingContext),
417 mipmapped,
418 policy,
419 /*label=*/"SkImageGpu_AsView"),
421 }
422 GrSurfaceProxyView view = this->makeView(recordingContext);
424 if (mipmapped == skgpu::Mipmapped::kYes) {
425 view = skgpu::ganesh::FindOrMakeCachedMipmappedView(recordingContext, std::move(view),
426 this->uniqueID());
427 }
428 return {std::move(view), ct};
429}
430
431std::unique_ptr<GrFragmentProcessor> SkImage_Ganesh::asFragmentProcessor(
432 GrRecordingContext* rContext,
434 const SkTileMode tileModes[2],
435 const SkMatrix& m,
436 const SkRect* subset,
437 const SkRect* domain) const {
438 if (!fContext->priv().matches(rContext)) {
439 return {};
440 }
441 auto mm =
444 rContext,
445 std::get<0>(skgpu::ganesh::AsView(rContext, this, mm)),
446 this->alphaType(),
447 sampling,
448 tileModes,
449 m,
450 subset,
451 domain);
452}
453
454GrSurfaceProxyView SkImage_Ganesh::makeView(GrRecordingContext* rContext) const {
455 return {fChooser.chooseProxy(rContext), fOrigin, fSwizzle};
456}
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
static constexpr SkColorType GrColorTypeToSkColorType(GrColorType ct)
Definition: GrTypesPriv.h:589
GrColorType
Definition: GrTypesPriv.h:540
static constexpr GrColorType SkColorTypeToGrColorType(SkColorType ct)
Definition: GrTypesPriv.h:629
GrSurfaceOrigin
Definition: GrTypes.h:147
GrSemaphoresSubmitted
Definition: GrTypes.h:229
#define SkASSERT(cond)
Definition: SkAssert.h:116
SkColorType
Definition: SkColorType.h:19
GrImageTexGenPolicy
Definition: SkGr.h:141
SkYUVColorSpace
Definition: SkImageInfo.h:68
@ kNeedNewImageUniqueID
Definition: SkImage_Base.h:33
#define INHERITED(method,...)
Definition: SkRecorder.cpp:128
sk_sp< T > sk_ref_sp(T *obj)
Definition: SkRefCnt.h:381
SkTileMode
Definition: SkTileMode.h:13
bool matches(GrContext_Base *candidate) const
const GrCaps * caps() const
Definition: GrCaps.h:57
bool areColorTypeAndFormatCompatible(GrColorType grCT, const GrBackendFormat &format) const
Definition: GrCaps.cpp:428
bool isFormatCompressed(const GrBackendFormat &format) const
Definition: GrCaps.cpp:457
static std::unique_ptr< GrFragmentProcessor > Make(std::unique_ptr< GrFragmentProcessor > child, SkColorSpace *src, SkAlphaType srcAT, SkColorSpace *dst, SkAlphaType dstAT)
virtual GrDirectContext * asDirectContext()
GrSemaphoresSubmitted flushSurface(GrSurfaceProxy *proxy, SkSurfaces::BackendSurfaceAccess access=SkSurfaces::BackendSurfaceAccess::kNoAccess, const GrFlushInfo &info={}, const skgpu::MutableTextureState *newState=nullptr)
bool abandoned() override
GrDirectContextPriv priv()
GrImageContextPriv priv()
std::unique_ptr< skgpu::ganesh::SurfaceFillContext > makeSFCWithFallback(GrImageInfo, SkBackingFit, int sampleCount, skgpu::Mipmapped, skgpu::Protected, GrSurfaceOrigin=kTopLeft_GrSurfaceOrigin, skgpu::Budgeted=skgpu::Budgeted::kYes)
std::unique_ptr< skgpu::ganesh::SurfaceContext > makeSC(GrSurfaceProxyView readView, const GrColorInfo &)
skgpu::Swizzle swizzle() const
GrSurfaceOrigin origin() const
GrSurfaceProxy * proxy() const
sk_sp< GrSurfaceProxy > refProxy() const
UniqueID underlyingUniqueID() const
int getTaskTargetCount() const
GrProtected isProtected() const
virtual bool instantiate(GrResourceProvider *)=0
GrTexture * peekTexture() const
static sk_sp< GrSurfaceProxy > Copy(GrRecordingContext *, sk_sp< GrSurfaceProxy > src, GrSurfaceOrigin, skgpu::Mipmapped, SkIRect srcRect, SkBackingFit, skgpu::Budgeted, std::string_view label, RectsMustMatch=RectsMustMatch::kNo, sk_sp< GrRenderTask > *outTask=nullptr)
virtual GrTextureProxy * asTextureProxy()
bool isInstantiated() const
static std::unique_ptr< GrFragmentProcessor > Make(GrSurfaceProxyView, SkAlphaType, const SkMatrix &=SkMatrix::I(), GrSamplerState::Filter=GrSamplerState::Filter::kNearest, GrSamplerState::MipmapMode mipmapMode=GrSamplerState::MipmapMode::kNone)
skgpu::Mipmapped mipmapped() const
sk_sp< SkImage > makeColorSpace(GrDirectContext *, sk_sp< SkColorSpace >) const override
sk_sp< GrImageContext > fContext
GrImageContext * context() const final
void generatingSurfaceIsDeleted() override
std::unique_ptr< GrFragmentProcessor > asFragmentProcessor(GrRecordingContext *, SkSamplingOptions, const SkTileMode[2], const SkMatrix &, const SkRect *, const SkRect *) const override
SkImage_Ganesh(sk_sp< GrImageContext > context, uint32_t uniqueID, GrSurfaceProxyView view, SkColorInfo info)
sk_sp< SkImage > onMakeColorTypeAndColorSpace(SkColorType, sk_sp< SkColorSpace >, GrDirectContext *) const final
bool onIsProtected() const override
sk_sp< SkImage > onReinterpretColorSpace(sk_sp< SkColorSpace >) const final
void onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace, bool readAlpha, sk_sp< SkColorSpace >, SkIRect srcRect, SkISize dstSize, RescaleGamma, RescaleMode, ReadPixelsCallback, ReadPixelsContext) const override
bool getExistingBackendTexture(GrBackendTexture *outTexture, bool flushPendingGrContextIO, GrSurfaceOrigin *origin) const
GrSurfaceOrigin origin() const override
bool onHasMipmaps() const override
GrSemaphoresSubmitted flush(GrDirectContext *, const GrFlushInfo &) const override
bool surfaceMustCopyOnWrite(GrSurfaceProxy *surfaceProxy) const
void onAsyncRescaleAndReadPixels(const SkImageInfo &, SkIRect srcRect, RescaleGamma, RescaleMode, ReadPixelsCallback, ReadPixelsContext) const override
size_t textureSize() const override
~SkImage_Ganesh() override
std::tuple< GrSurfaceProxyView, GrColorType > asView(GrRecordingContext *, skgpu::Mipmapped, GrImageTexGenPolicy) const override
static sk_sp< SkImage > MakeWithVolatileSrc(sk_sp< GrRecordingContext > rContext, GrSurfaceProxyView volatileSrc, SkColorInfo colorInfo)
const SkImageInfo & imageInfo() const
Definition: SkImage.h:279
SkISize dimensions() const
Definition: SkImage.h:297
uint32_t uniqueID() const
Definition: SkImage.h:311
void * ReadPixelsContext
Definition: SkImage.h:578
SkAlphaType alphaType() const
Definition: SkImage.cpp:154
RescaleMode
Definition: SkImage.h:587
RescaleGamma
Definition: SkImage.h:585
SkColorType colorType() const
Definition: SkImage.cpp:152
bool hasMipmaps() const
Definition: SkImage.cpp:292
void(ReadPixelsContext, std::unique_ptr< const AsyncReadResult >) ReadPixelsCallback
Definition: SkImage.h:583
T * get() const
Definition: SkRefCnt.h:303
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
uint32_t uint32_t * format
FlTexture * texture
SK_API sk_sp< SkDocument > Make(SkWStream *dst, const SkSerialProcs *=nullptr, std::function< void(const SkPicture *)> onEndPage=nullptr)
SkSamplingOptions sampling
Definition: SkRecords.h:337
@ kNoAccess
back-end surface will not be used by client
Definition: copy.py:1
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network policy
Definition: switches.h:248
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
const myers::Point & get< 0 >(const myers::Segment &s)
Definition: Myers.h:80
GrSurfaceProxyView FindOrMakeCachedMipmappedView(GrRecordingContext *rContext, GrSurfaceProxyView view, uint32_t imageUniqueID)
GrSurfaceProxyView CopyView(GrRecordingContext *context, GrSurfaceProxyView src, skgpu::Mipmapped mipmapped, GrImageTexGenPolicy policy, std::string_view label)
std::tuple< GrSurfaceProxyView, GrColorType > AsView(GrRecordingContext *rContext, const SkImage *img, skgpu::Mipmapped mipmapped, GrImageTexGenPolicy policy)
std::unique_ptr< GrFragmentProcessor > MakeFragmentProcessorFromView(GrRecordingContext *rContext, GrSurfaceProxyView view, SkAlphaType at, SkSamplingOptions sampling, const SkTileMode tileModes[2], const SkMatrix &m, const SkRect *subset, const SkRect *domain)
Mipmapped
Definition: GpuTypes.h:53
Protected
Definition: GpuTypes.h:61
Definition: ref_ptr.h:256
Definition: SkRect.h:32
Definition: SkSize.h:16
const SkMipmapMode mipmap