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 "src/gpu/graphite/vk/VulkanTexture.h"
9
10 #include "include/gpu/MutableTextureState.h"
11 #include "include/gpu/vk/VulkanMutableTextureState.h"
12 #include "src/core/SkMipmap.h"
13 #include "src/gpu/graphite/Log.h"
14 #include "src/gpu/graphite/vk/VulkanCaps.h"
15 #include "src/gpu/graphite/vk/VulkanCommandBuffer.h"
16 #include "src/gpu/graphite/vk/VulkanGraphiteUtilsPriv.h"
17 #include "src/gpu/graphite/vk/VulkanResourceProvider.h"
18 #include "src/gpu/graphite/vk/VulkanSharedContext.h"
19 #include "src/gpu/vk/VulkanMemory.h"
20 #include "src/gpu/vk/VulkanMutableTextureStatePriv.h"
21
22 namespace skgpu::graphite {
23
MakeVkImage(const VulkanSharedContext * sharedContext,SkISize dimensions,const TextureInfo & info,CreatedImageInfo * outInfo)24 bool VulkanTexture::MakeVkImage(const VulkanSharedContext* sharedContext,
25 SkISize dimensions,
26 const TextureInfo& info,
27 CreatedImageInfo* outInfo) {
28 SkASSERT(outInfo);
29 const VulkanCaps& caps = sharedContext->vulkanCaps();
30
31 if (dimensions.isEmpty()) {
32 SKGPU_LOG_E("Tried to create VkImage with empty dimensions.");
33 return false;
34 }
35 if (dimensions.width() > caps.maxTextureSize() ||
36 dimensions.height() > caps.maxTextureSize()) {
37 SKGPU_LOG_E("Tried to create VkImage with too large a size.");
38 return false;
39 }
40
41 if ((info.isProtected() == Protected::kYes) != caps.protectedSupport()) {
42 SKGPU_LOG_E("Tried to create %s VkImage in %s Context.",
43 info.isProtected() == Protected::kYes ? "protected" : "unprotected",
44 caps.protectedSupport() ? "protected" : "unprotected");
45 return false;
46 }
47
48 const VulkanTextureSpec& spec = info.vulkanTextureSpec();
49
50 bool isLinear = spec.fImageTiling == VK_IMAGE_TILING_LINEAR;
51 VkImageLayout initialLayout = isLinear ? VK_IMAGE_LAYOUT_PREINITIALIZED
52 : VK_IMAGE_LAYOUT_UNDEFINED;
53
54 // Create Image
55 VkSampleCountFlagBits vkSamples;
56 if (!SampleCountToVkSampleCount(info.numSamples(), &vkSamples)) {
57 SKGPU_LOG_E("Failed creating VkImage because we could not covert the number of samples: "
58 "%u to a VkSampleCountFlagBits.", info.numSamples());
59 return false;
60 }
61
62 SkASSERT(!isLinear || vkSamples == VK_SAMPLE_COUNT_1_BIT);
63
64 VkImageCreateFlags createflags = 0;
65 if (info.isProtected() == Protected::kYes && caps.protectedSupport()) {
66 createflags |= VK_IMAGE_CREATE_PROTECTED_BIT;
67 }
68
69 uint32_t numMipLevels = 1;
70 if (info.mipmapped() == Mipmapped::kYes) {
71 numMipLevels = SkMipmap::ComputeLevelCount(dimensions.width(), dimensions.height()) + 1;
72 }
73
74 uint32_t width = static_cast<uint32_t>(dimensions.fWidth);
75 uint32_t height = static_cast<uint32_t>(dimensions.fHeight);
76
77 const VkImageCreateInfo imageCreateInfo = {
78 VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // sType
79 nullptr, // pNext
80 createflags, // VkImageCreateFlags
81 VK_IMAGE_TYPE_2D, // VkImageType
82 spec.fFormat, // VkFormat
83 { width, height, 1 }, // VkExtent3D
84 numMipLevels, // mipLevels
85 1, // arrayLayers
86 vkSamples, // samples
87 spec.fImageTiling, // VkImageTiling
88 spec.fImageUsageFlags, // VkImageUsageFlags
89 spec.fSharingMode, // VkSharingMode
90 0, // queueFamilyCount
91 nullptr, // pQueueFamilyIndices
92 initialLayout // initialLayout
93 };
94
95 auto device = sharedContext->device();
96
97 VkImage image = VK_NULL_HANDLE;
98 VkResult result;
99 VULKAN_CALL_RESULT(
100 sharedContext, result, CreateImage(device, &imageCreateInfo, nullptr, &image));
101 if (result != VK_SUCCESS) {
102 SKGPU_LOG_E("Failed call to vkCreateImage with error: %d", result);
103 return false;
104 }
105
106 auto allocator = sharedContext->memoryAllocator();
107 bool forceDedicatedMemory = caps.shouldAlwaysUseDedicatedImageMemory();
108 bool useLazyAllocation =
109 SkToBool(spec.fImageUsageFlags & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT);
110
111 auto checkResult = [sharedContext](VkResult result) {
112 return sharedContext->checkVkResult(result);
113 };
114 if (!skgpu::VulkanMemory::AllocImageMemory(allocator,
115 image,
116 info.isProtected(),
117 forceDedicatedMemory,
118 useLazyAllocation,
119 checkResult,
120 &outInfo->fMemoryAlloc)) {
121 VULKAN_CALL(sharedContext->interface(), DestroyImage(device, image, nullptr));
122 return false;
123 }
124
125 if (useLazyAllocation &&
126 !SkToBool(outInfo->fMemoryAlloc.fFlags & skgpu::VulkanAlloc::kLazilyAllocated_Flag)) {
127 SKGPU_LOG_E("Failed allocate lazy vulkan memory when requested");
128 skgpu::VulkanMemory::FreeImageMemory(allocator, outInfo->fMemoryAlloc);
129 return false;
130 }
131
132 VULKAN_CALL_RESULT(
133 sharedContext,
134 result,
135 BindImageMemory(
136 device, image, outInfo->fMemoryAlloc.fMemory, outInfo->fMemoryAlloc.fOffset));
137 if (result != VK_SUCCESS) {
138 skgpu::VulkanMemory::FreeImageMemory(allocator, outInfo->fMemoryAlloc);
139 VULKAN_CALL(sharedContext->interface(), DestroyImage(device, image, nullptr));
140 return false;
141 }
142
143 outInfo->fImage = image;
144 outInfo->fMutableState = sk_make_sp<MutableTextureState>(
145 skgpu::MutableTextureStates::MakeVulkan(initialLayout, VK_QUEUE_FAMILY_IGNORED));
146 return true;
147 }
148
Make(const VulkanSharedContext * sharedContext,SkISize dimensions,const TextureInfo & info,skgpu::Budgeted budgeted,sk_sp<VulkanYcbcrConversion> ycbcrConversion)149 sk_sp<Texture> VulkanTexture::Make(const VulkanSharedContext* sharedContext,
150 SkISize dimensions,
151 const TextureInfo& info,
152 skgpu::Budgeted budgeted,
153 sk_sp<VulkanYcbcrConversion> ycbcrConversion) {
154 CreatedImageInfo imageInfo;
155 if (!MakeVkImage(sharedContext, dimensions, info, &imageInfo)) {
156 return nullptr;
157 }
158
159 return sk_sp<Texture>(new VulkanTexture(sharedContext,
160 dimensions,
161 info,
162 std::move(imageInfo.fMutableState),
163 imageInfo.fImage,
164 imageInfo.fMemoryAlloc,
165 Ownership::kOwned,
166 budgeted,
167 std::move(ycbcrConversion)));
168 }
169
MakeWrapped(const VulkanSharedContext * sharedContext,SkISize dimensions,const TextureInfo & info,sk_sp<MutableTextureState> mutableState,VkImage image,const VulkanAlloc & alloc,sk_sp<VulkanYcbcrConversion> ycbcrConversion)170 sk_sp<Texture> VulkanTexture::MakeWrapped(const VulkanSharedContext* sharedContext,
171 SkISize dimensions,
172 const TextureInfo& info,
173 sk_sp<MutableTextureState> mutableState,
174 VkImage image,
175 const VulkanAlloc& alloc,
176 sk_sp<VulkanYcbcrConversion> ycbcrConversion) {
177 return sk_sp<Texture>(new VulkanTexture(sharedContext,
178 dimensions,
179 info,
180 std::move(mutableState),
181 image,
182 alloc,
183 Ownership::kWrapped,
184 skgpu::Budgeted::kNo,
185 std::move(ycbcrConversion)));
186 }
187
vk_format_to_aspect_flags(VkFormat format)188 VkImageAspectFlags vk_format_to_aspect_flags(VkFormat format) {
189 switch (format) {
190 case VK_FORMAT_S8_UINT:
191 return VK_IMAGE_ASPECT_STENCIL_BIT;
192 case VK_FORMAT_D16_UNORM:
193 [[fallthrough]];
194 case VK_FORMAT_D32_SFLOAT:
195 return VK_IMAGE_ASPECT_DEPTH_BIT;
196 case VK_FORMAT_D24_UNORM_S8_UINT:
197 [[fallthrough]];
198 case VK_FORMAT_D32_SFLOAT_S8_UINT:
199 return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
200 default:
201 return VK_IMAGE_ASPECT_COLOR_BIT;
202 }
203 }
204
setImageLayoutAndQueueIndex(VulkanCommandBuffer * cmdBuffer,VkImageLayout newLayout,VkAccessFlags dstAccessMask,VkPipelineStageFlags dstStageMask,bool byRegion,uint32_t newQueueFamilyIndex) const205 void VulkanTexture::setImageLayoutAndQueueIndex(VulkanCommandBuffer* cmdBuffer,
206 VkImageLayout newLayout,
207 VkAccessFlags dstAccessMask,
208 VkPipelineStageFlags dstStageMask,
209 bool byRegion,
210 uint32_t newQueueFamilyIndex) const {
211
212 SkASSERT(newLayout == this->currentLayout() ||
213 (VK_IMAGE_LAYOUT_UNDEFINED != newLayout &&
214 VK_IMAGE_LAYOUT_PREINITIALIZED != newLayout));
215 VkImageLayout currentLayout = this->currentLayout();
216 uint32_t currentQueueIndex = this->currentQueueFamilyIndex();
217
218 VulkanTextureInfo textureInfo;
219 this->textureInfo().getVulkanTextureInfo(&textureInfo);
220 auto sharedContext = static_cast<const VulkanSharedContext*>(this->sharedContext());
221
222 // Enable the following block on new devices to test that their lazy images stay at 0 memory use
223 #if 0
224 auto device = sharedContext->device();
225 if (fAlloc.fFlags & skgpu::VulkanAlloc::kLazilyAllocated_Flag) {
226 VkDeviceSize size;
227 VULKAN_CALL(sharedContext->interface(), GetDeviceMemoryCommitment(device, fAlloc.fMemory, &size));
228
229 SkDebugf("Lazy Image. This: %p, image: %d, size: %d\n", this, fImage, size);
230 }
231 #endif
232 #ifdef SK_DEBUG
233 if (textureInfo.fSharingMode == VK_SHARING_MODE_CONCURRENT) {
234 if (newQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED) {
235 SkASSERT(currentQueueIndex == VK_QUEUE_FAMILY_IGNORED ||
236 currentQueueIndex == VK_QUEUE_FAMILY_EXTERNAL ||
237 currentQueueIndex == VK_QUEUE_FAMILY_FOREIGN_EXT);
238 } else {
239 SkASSERT(newQueueFamilyIndex == VK_QUEUE_FAMILY_EXTERNAL ||
240 newQueueFamilyIndex == VK_QUEUE_FAMILY_FOREIGN_EXT);
241 SkASSERT(currentQueueIndex == VK_QUEUE_FAMILY_IGNORED);
242 }
243 } else {
244 SkASSERT(textureInfo.fSharingMode == VK_SHARING_MODE_EXCLUSIVE);
245 if (newQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED ||
246 currentQueueIndex == sharedContext->queueIndex()) {
247 SkASSERT(currentQueueIndex == VK_QUEUE_FAMILY_IGNORED ||
248 currentQueueIndex == VK_QUEUE_FAMILY_EXTERNAL ||
249 currentQueueIndex == VK_QUEUE_FAMILY_FOREIGN_EXT ||
250 currentQueueIndex == sharedContext->queueIndex());
251 } else if (newQueueFamilyIndex == VK_QUEUE_FAMILY_EXTERNAL ||
252 newQueueFamilyIndex == VK_QUEUE_FAMILY_FOREIGN_EXT) {
253 SkASSERT(currentQueueIndex == VK_QUEUE_FAMILY_IGNORED ||
254 currentQueueIndex == sharedContext->queueIndex());
255 }
256 }
257 #endif
258
259 if (textureInfo.fSharingMode == VK_SHARING_MODE_EXCLUSIVE) {
260 if (newQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED) {
261 newQueueFamilyIndex = sharedContext->queueIndex();
262 }
263 if (currentQueueIndex == VK_QUEUE_FAMILY_IGNORED) {
264 currentQueueIndex = sharedContext->queueIndex();
265 }
266 }
267
268 // If the old and new layout are the same and the layout is a read only layout, there is no need
269 // to put in a barrier unless we also need to switch queues.
270 if (newLayout == currentLayout && currentQueueIndex == newQueueFamilyIndex &&
271 (VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL == currentLayout ||
272 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL == currentLayout ||
273 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL == currentLayout)) {
274 return;
275 }
276
277 VkAccessFlags srcAccessMask = VulkanTexture::LayoutToSrcAccessMask(currentLayout);
278 VkPipelineStageFlags srcStageMask = VulkanTexture::LayoutToPipelineSrcStageFlags(currentLayout);
279
280 VkImageAspectFlags aspectFlags = vk_format_to_aspect_flags(textureInfo.fFormat);
281 uint32_t numMipLevels = 1;
282 SkISize dimensions = this->dimensions();
283 if (this->mipmapped() == Mipmapped::kYes) {
284 numMipLevels = SkMipmap::ComputeLevelCount(dimensions.width(), dimensions.height()) + 1;
285 }
286 VkImageMemoryBarrier imageMemoryBarrier = {
287 VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, // sType
288 nullptr, // pNext
289 srcAccessMask, // srcAccessMask
290 dstAccessMask, // dstAccessMask
291 currentLayout, // oldLayout
292 newLayout, // newLayout
293 currentQueueIndex, // srcQueueFamilyIndex
294 newQueueFamilyIndex, // dstQueueFamilyIndex
295 fImage, // image
296 { aspectFlags, 0, numMipLevels, 0, 1 } // subresourceRange
297 };
298 SkASSERT(srcAccessMask == imageMemoryBarrier.srcAccessMask);
299 cmdBuffer->addImageMemoryBarrier(this, srcStageMask, dstStageMask, byRegion,
300 &imageMemoryBarrier);
301
302 skgpu::MutableTextureStates::SetVkImageLayout(this->mutableState(), newLayout);
303 skgpu::MutableTextureStates::SetVkQueueFamilyIndex(this->mutableState(), newQueueFamilyIndex);
304 }
305
VulkanTexture(const VulkanSharedContext * sharedContext,SkISize dimensions,const TextureInfo & info,sk_sp<MutableTextureState> mutableState,VkImage image,const VulkanAlloc & alloc,Ownership ownership,skgpu::Budgeted budgeted,sk_sp<VulkanYcbcrConversion> ycbcrConversion)306 VulkanTexture::VulkanTexture(const VulkanSharedContext* sharedContext,
307 SkISize dimensions,
308 const TextureInfo& info,
309 sk_sp<MutableTextureState> mutableState,
310 VkImage image,
311 const VulkanAlloc& alloc,
312 Ownership ownership,
313 skgpu::Budgeted budgeted,
314 sk_sp<VulkanYcbcrConversion> ycbcrConversion)
315 : Texture(sharedContext, dimensions, info, std::move(mutableState), ownership, budgeted)
316 , fImage(image)
317 , fMemoryAlloc(alloc)
318 , fYcbcrConversion(std::move(ycbcrConversion)) {}
319
freeGpuData()320 void VulkanTexture::freeGpuData() {
321 // Need to delete any ImageViews first
322 fImageViews.clear();
323
324 // If the texture is wrapped we don't own this data
325 if (this->ownership() != Ownership::kWrapped) {
326 auto sharedContext = static_cast<const VulkanSharedContext*>(this->sharedContext());
327 VULKAN_CALL(sharedContext->interface(),
328 DestroyImage(sharedContext->device(), fImage, nullptr));
329 skgpu::VulkanMemory::FreeImageMemory(sharedContext->memoryAllocator(), fMemoryAlloc);
330 }
331 }
332
updateImageLayout(VkImageLayout newLayout)333 void VulkanTexture::updateImageLayout(VkImageLayout newLayout) {
334 skgpu::MutableTextureStates::SetVkImageLayout(this->mutableState(), newLayout);
335 }
336
currentLayout() const337 VkImageLayout VulkanTexture::currentLayout() const {
338 return skgpu::MutableTextureStates::GetVkImageLayout(this->mutableState());
339 }
340
currentQueueFamilyIndex() const341 uint32_t VulkanTexture::currentQueueFamilyIndex() const {
342 return skgpu::MutableTextureStates::GetVkQueueFamilyIndex(this->mutableState());
343 }
344
LayoutToPipelineSrcStageFlags(const VkImageLayout layout)345 VkPipelineStageFlags VulkanTexture::LayoutToPipelineSrcStageFlags(const VkImageLayout layout) {
346 if (VK_IMAGE_LAYOUT_GENERAL == layout) {
347 return VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
348 } else if (VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL == layout ||
349 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL == layout) {
350 return VK_PIPELINE_STAGE_TRANSFER_BIT;
351 } else if (VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL == layout) {
352 return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
353 } else if (VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL == layout ||
354 VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL == layout) {
355 return VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
356 } else if (VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL == layout) {
357 return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
358 } else if (VK_IMAGE_LAYOUT_PREINITIALIZED == layout) {
359 return VK_PIPELINE_STAGE_HOST_BIT;
360 } else if (VK_IMAGE_LAYOUT_PRESENT_SRC_KHR == layout) {
361 return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
362 }
363
364 SkASSERT(VK_IMAGE_LAYOUT_UNDEFINED == layout);
365 return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
366 }
367
LayoutToSrcAccessMask(const VkImageLayout layout)368 VkAccessFlags VulkanTexture::LayoutToSrcAccessMask(const VkImageLayout layout) {
369 // Currently we assume we will never being doing any explict shader writes (this doesn't include
370 // color attachment or depth/stencil writes). So we will ignore the
371 // VK_MEMORY_OUTPUT_SHADER_WRITE_BIT.
372
373 // We can only directly access the host memory if we are in preinitialized or general layout,
374 // and the image is linear.
375 // TODO: Add check for linear here so we are not always adding host to general, and we should
376 // only be in preinitialized if we are linear
377 VkAccessFlags flags = 0;
378 if (VK_IMAGE_LAYOUT_GENERAL == layout) {
379 flags = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
380 VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
381 VK_ACCESS_TRANSFER_WRITE_BIT |
382 VK_ACCESS_HOST_WRITE_BIT;
383 } else if (VK_IMAGE_LAYOUT_PREINITIALIZED == layout) {
384 flags = VK_ACCESS_HOST_WRITE_BIT;
385 } else if (VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL == layout) {
386 flags = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
387 } else if (VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL == layout) {
388 flags = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
389 } else if (VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL == layout) {
390 flags = VK_ACCESS_TRANSFER_WRITE_BIT;
391 } else if (VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL == layout ||
392 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL == layout ||
393 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR == layout) {
394 // There are no writes that need to be made available
395 flags = 0;
396 }
397 return flags;
398 }
399
getImageView(VulkanImageView::Usage usage) const400 const VulkanImageView* VulkanTexture::getImageView(VulkanImageView::Usage usage) const {
401 for (int i = 0; i < fImageViews.size(); ++i) {
402 if (fImageViews[i]->usage() == usage) {
403 return fImageViews[i].get();
404 }
405 }
406
407 auto sharedContext = static_cast<const VulkanSharedContext*>(this->sharedContext());
408 VulkanTextureInfo vkTexInfo;
409 this->textureInfo().getVulkanTextureInfo(&vkTexInfo);
410 int miplevels = this->textureInfo().mipmapped() == Mipmapped::kYes
411 ? SkMipmap::ComputeLevelCount(this->dimensions().width(),
412 this->dimensions().height()) + 1
413 : 1;
414 auto imageView = VulkanImageView::Make(sharedContext,
415 fImage,
416 vkTexInfo.fFormat,
417 usage,
418 miplevels,
419 fYcbcrConversion);
420 return fImageViews.push_back(std::move(imageView)).get();
421 }
422
423
424 } // namespace skgpu::graphite
425