| /* | |
| * Copyright 2026 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "src/gpu/graphite/StorageContext.h" | |
| #include "include/private/SkAlign.h" | |
| #include "include/private/SkAttributes.h" | |
| #include "src/gpu/graphite/BufferManager.h" | |
| #include "src/gpu/graphite/Caps.h" | |
| #include "src/gpu/graphite/DrawContext.h" | |
| #include "src/gpu/graphite/RecorderPriv.h" | |
| #include "src/gpu/graphite/TextureProxy.h" | |
| #include "src/gpu/graphite/TextureProxyView.h" | |
| #include "src/gpu/graphite/task/UploadTask.h" | |
| #include "src/shaders/gradients/SkGradientBaseShader.h" | |
| namespace skgpu::graphite { | |
| namespace { | |
| sk_sp<TextureProxy> create_texture_proxy(Recorder* recorder, int width, int height) { | |
| const Caps* caps = recorder->priv().caps(); | |
| TextureInfo info = caps->getDefaultReadableTextureInfo( | |
| StorageContext::kTextureFormat, | |
| recorder->priv().isProtected()); | |
| sk_sp<TextureProxy> proxy = TextureProxy::Make(caps, | |
| recorder->priv().resourceProvider(), | |
| SkISize::Make(width, height), | |
| info, | |
| Budgeted::kYes, | |
| "StorageFallbackTexture"); | |
| return proxy; | |
| } | |
| } // anonymous namespace | |
| StorageContext::StorageContext(const ResourceBindingRequirements& bindingReqs, | |
| bool storageBufferSupport) | |
| : StorageContext(bindingReqs.fMaxFallbackTextureSize, | |
| storageBufferSupport, | |
| bindingReqs.fMaxStorageBufferBytes) {} | |
| StorageContext::StorageContext(int maxFallbackTextureSize, | |
| bool storageBufferSupport, | |
| int maxStorageBufferBytes) | |
| : fRunningLCM(1) | |
| , fMaxFallbackTextureSize(maxFallbackTextureSize) | |
| , fMaxStorageBytes(storageBufferSupport | |
| ? maxStorageBufferBytes | |
| : maxFallbackTextureSize * maxFallbackTextureSize * kTexelBytes) | |
| , fStorageBufferSupport(storageBufferSupport) { | |
| SkASSERT(fMaxFallbackTextureSize > 0); | |
| SkASSERT(fMaxStorageBytes > 0); | |
| } | |
| StorageContext::~StorageContext() { | |
| this->resetCache(); | |
| } | |
| void StorageContext::GradientCache::reset() { | |
| fLocalGradientOffsetCache.foreach([](const SkGradientBaseShader* shader, int*) { | |
| shader->unref(); | |
| }); | |
| fLocalGradientOffsetCache.reset(); | |
| fGradientData.clear(); | |
| fGradientDataSize = 0; | |
| } | |
| void StorageContext::resetCache() { | |
| fGradientCache.reset(); | |
| fVertexData.clear(); | |
| fRunningLCM = 1; | |
| SkDEBUGCODE(fFinalized = false;) | |
| } | |
| std::pair<float*, int> StorageContext::allocateGradientData(int numStops, | |
| const SkGradientBaseShader* shader) { | |
| SkASSERT(!fFinalized); | |
| if (numStops < 1 || numStops > GradientCache::kMaxGradientStops) { | |
| return {nullptr, -1}; | |
| } | |
| int* existingLocalOffset = fGradientCache.fLocalGradientOffsetCache.find(shader); | |
| if (existingLocalOffset) { | |
| return {nullptr, *existingLocalOffset}; | |
| } | |
| int floatOffset = fGradientCache.fGradientData.size(); | |
| const int floatCount = fStorageBufferSupport | |
| ? (numStops * 5) | |
| : (SkAlign4(numStops) + numStops * 4); | |
| SkASSERT(fStorageBufferSupport || SkIsAlign4(floatCount)); | |
| const int maxStorageFloats = fMaxStorageBytes / static_cast<int>(sizeof(float)); | |
| if (floatCount > maxStorageFloats || maxStorageFloats - floatCount < floatOffset) { | |
| return {nullptr, -1}; | |
| } | |
| fGradientCache.fGradientData.resize(floatOffset + floatCount); | |
| float* dstData = SK_UNSAFE_TODO(fGradientCache.fGradientData.data() + floatOffset); | |
| SK_UNSAFE_TODO(memset(dstData, 0, floatCount * sizeof(float))); | |
| shader->ref(); | |
| fGradientCache.fLocalGradientOffsetCache.set(shader, floatOffset); | |
| fGradientCache.fGradientDataSize = fGradientCache.fGradientData.size_bytes(); | |
| return {dstData, floatOffset}; | |
| } | |
| void StorageContext::recordAlignment(size_t stride, size_t align) { | |
| SkASSERT(stride > 0 && align > 0); | |
| SkASSERT(!fFinalized); | |
| if (!fStorageBufferSupport) { | |
| align = std::max<size_t>(align, kTexelBytes); | |
| stride = SkAlignTo<size_t>(stride, kTexelBytes); | |
| } | |
| uint32_t align32 = BufferAligner::LcmAlignment(SkTo<uint32_t>(align), SkTo<uint32_t>(stride)); | |
| fRunningLCM = BufferAligner::LcmAlignment(fRunningLCM, align32); | |
| } | |
| uint32_t StorageContext::appendVertices(const void* data, | |
| size_t count, | |
| size_t stride, | |
| size_t align) { | |
| SkASSERT(data && count > 0 && stride > 0 && align > 0); | |
| size_t paddedStride = stride; | |
| size_t paddedAlign = align; | |
| if (!fStorageBufferSupport) { | |
| paddedStride = SkAlignTo<size_t>(stride, kTexelBytes); | |
| paddedAlign = std::max<size_t>(align, static_cast<size_t>(kTexelBytes)); | |
| } | |
| uint32_t align32 = BufferAligner::LcmAlignment(SkTo<uint32_t>(paddedAlign), | |
| SkTo<uint32_t>(paddedStride)); | |
| SkASSERT(fRunningLCM % align32 == 0); | |
| uint32_t requiredBytes = | |
| BufferAligner::ValidateCountAndStride(count, paddedStride, /*headroom=*/0, align32); | |
| if (requiredBytes == 0) { | |
| return 0; | |
| } | |
| uint32_t alignedVertOffset = SkAlignNonPow2(static_cast<uint32_t>(fVertexData.size()), align32); | |
| const size_t maxStorageBytes = static_cast<size_t>(fMaxStorageBytes); | |
| size_t newVertBytes = static_cast<size_t>(alignedVertOffset) + requiredBytes; | |
| if (fGradientCache.fGradientDataSize > maxStorageBytes || | |
| newVertBytes > maxStorageBytes - fGradientCache.fGradientDataSize) { | |
| return 0; | |
| } | |
| if (alignedVertOffset > static_cast<uint32_t>(fVertexData.size())) { | |
| int padBytes = alignedVertOffset - fVertexData.size(); | |
| SK_UNSAFE_TODO(memset(fVertexData.append(padBytes), 0, padBytes)); | |
| } | |
| char* dst = fVertexData.append(requiredBytes); | |
| // Because the fallback texture format is kRGBA32F (16 bytes per texel), the unpack shader | |
| // addresses instances by whole texels (index * nTexels). Pad each instance's stride to a | |
| // 16-byte texel boundary so subsequent instances align with the shader's texel indexing. | |
| if (stride == paddedStride) { | |
| SK_UNSAFE_TODO(memcpy(dst, data, requiredBytes)); | |
| } else { | |
| const char* src = static_cast<const char*>(data); | |
| size_t diff = paddedStride - stride; | |
| for (size_t i = 0; i < count; ++i) { | |
| SK_UNSAFE_TODO(memcpy(dst, src, stride)); | |
| SK_UNSAFE_TODO(memset(dst + stride, 0, diff)); | |
| SK_UNSAFE_TODO(dst += paddedStride); | |
| SK_UNSAFE_TODO(src += stride); | |
| } | |
| } | |
| return SkTo<uint32_t>(fGradientCache.fGradientDataSize) + alignedVertOffset; | |
| } | |
| void StorageContext::finalizePrecachedStorageData() { | |
| fGradientCache.fGradientDataSize = | |
| SkAlignNonPow2<size_t>(fGradientCache.fGradientData.size_bytes(), fRunningLCM); | |
| SkDEBUGCODE(fFinalized = true;) | |
| } | |
| std::optional<StorageContextResult> StorageContext::finalize(Recorder* recorder, | |
| DrawContext* drawContext) { | |
| SkASSERT(recorder); | |
| SkASSERT(fFinalized); | |
| SkDEBUGCODE(fFinalized = false;) | |
| std::optional<StorageContextResult> result; | |
| if (!this->isEmpty()) { | |
| if (fStorageBufferSupport) { | |
| if (BindBufferInfo info = this->finalizeStorageBuffer(recorder)) { | |
| result = info; | |
| } | |
| } else { | |
| if (sk_sp<TextureProxy> proxy = this->finalizeTexture(recorder, drawContext)) { | |
| result = std::move(proxy); | |
| } | |
| } | |
| } | |
| // Vertex data and LCM alignment are per-DrawPass, whereas the gradient cache persists across | |
| // internal DrawPass flushes until Recorder::snap() calls resetCache(). | |
| fVertexData.clear(); | |
| fRunningLCM = 1; | |
| fGradientCache.fGradientDataSize = fGradientCache.fGradientData.size_bytes(); | |
| return result; | |
| } | |
| BindBufferInfo StorageContext::finalizeStorageBuffer(Recorder* recorder) { | |
| DrawBufferManager* bufferMgr = recorder->priv().drawBufferManager(); | |
| SkASSERT(bufferMgr); | |
| size_t totalBytes = fGradientCache.fGradientDataSize + fVertexData.size_bytes(); | |
| if (totalBytes == 0 || totalBytes > static_cast<size_t>(fMaxStorageBytes)) { | |
| return {}; | |
| } | |
| BindBufferInfo result; | |
| auto [writer, bufferInfo, _] = bufferMgr->getMappedStorageBuffer(totalBytes, /*stride=*/1); | |
| if (writer) { | |
| if (!fGradientCache.isEmpty()) { | |
| writer.write(fGradientCache.fGradientData.data(), | |
| fGradientCache.fGradientData.size_bytes()); | |
| if (fGradientCache.fGradientDataSize > fGradientCache.fGradientData.size_bytes()) { | |
| writer.zeroBytes(fGradientCache.fGradientDataSize - | |
| fGradientCache.fGradientData.size_bytes()); | |
| } | |
| } | |
| if (!fVertexData.empty()) { | |
| writer.write(fVertexData.data(), fVertexData.size_bytes()); | |
| } | |
| result = bufferInfo; | |
| } | |
| return result; | |
| } | |
| // TODO (thomsmit): Currently UploadSource holds its own copy of the data. Create an alternative | |
| // path for uploading which allows writing to the mapped gpu buffer directly. | |
| sk_sp<TextureProxy> StorageContext::finalizeTexture(Recorder* recorder, DrawContext* drawContext) { | |
| SkASSERT(drawContext); | |
| size_t gradSize = fGradientCache.fGradientDataSize; | |
| size_t vertSize = fVertexData.size_bytes(); | |
| size_t totalBytes = gradSize + vertSize; | |
| if (totalBytes == 0 || totalBytes > static_cast<size_t>(fMaxStorageBytes)) { | |
| return nullptr; | |
| } | |
| int totalTexels = (totalBytes + kTexelBytes - 1) / kTexelBytes; | |
| int width = std::min(totalTexels, fMaxFallbackTextureSize); | |
| int height = (totalTexels + fMaxFallbackTextureSize - 1) / fMaxFallbackTextureSize; | |
| if (height > fMaxFallbackTextureSize) { | |
| return nullptr; | |
| } | |
| int atlasRowBytes = width * kTexelBytes; | |
| size_t paddedBytes = static_cast<size_t>(height) * atlasRowBytes; | |
| SkTDArray<char> uploadBuffer; | |
| uploadBuffer.resize(paddedBytes); | |
| SK_UNSAFE_TODO(memset(uploadBuffer.data(), 0, paddedBytes)); | |
| if (gradSize > 0) { | |
| SK_UNSAFE_TODO(memcpy(uploadBuffer.data(), | |
| fGradientCache.fGradientData.data(), | |
| fGradientCache.fGradientData.size_bytes())); | |
| } | |
| if (!fVertexData.empty()) { | |
| SK_UNSAFE_TODO(memcpy( | |
| uploadBuffer.data() + gradSize, fVertexData.data(), fVertexData.size_bytes())); | |
| } | |
| sk_sp<TextureProxy> proxy = create_texture_proxy(recorder, width, height); | |
| if (!proxy) { | |
| return nullptr; | |
| } | |
| MipLevel level; | |
| level.fPixels = uploadBuffer.data(); | |
| level.fRowBytes = atlasRowBytes; | |
| SkIRect dstRect = SkIRect::MakeWH(width, height); | |
| Swizzle readSwizzle = ReadSwizzleForColorType(StorageContext::kColorType, proxy->format()); | |
| TextureProxyView proxyView(std::move(proxy), readSwizzle); | |
| SkColorInfo colorInfo(StorageContext::kColorType, kPremul_SkAlphaType, nullptr); | |
| UploadSource source = UploadSource::Make(recorder->priv().caps(), | |
| proxyView, | |
| colorInfo, | |
| colorInfo, | |
| SkSpan<const MipLevel>(&level, 1), | |
| dstRect); | |
| UploadInstance uploadInstance = UploadInstance::Make(recorder, source, nullptr); | |
| if (!uploadInstance.isValid()) { | |
| return nullptr; | |
| } | |
| sk_sp<Task> uploadTask = UploadTask::Make(std::move(uploadInstance)); | |
| if (!uploadTask) { | |
| return nullptr; | |
| } | |
| drawContext->recordDependency(std::move(uploadTask)); | |
| return proxyView.refProxy(); | |
| } | |
| } // namespace skgpu::graphite |