Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/GPU/Vulkan/VulkanImage.cpp
3187 views
1
#include <algorithm>
2
3
#include "Common/Log.h"
4
#include "Common/GPU/Vulkan/VulkanContext.h"
5
#include "Common/GPU/Vulkan/VulkanAlloc.h"
6
#include "Common/GPU/Vulkan/VulkanImage.h"
7
#include "Common/GPU/Vulkan/VulkanMemory.h"
8
#include "Common/GPU/Vulkan/VulkanBarrier.h"
9
#include "Common/StringUtils.h"
10
11
using namespace PPSSPP_VK;
12
13
VulkanTexture::VulkanTexture(VulkanContext *vulkan, const char *tag)
14
: vulkan_(vulkan) {
15
truncate_cpy(tag_, tag);
16
}
17
18
void VulkanTexture::Wipe() {
19
if (view_ != VK_NULL_HANDLE) {
20
vulkan_->Delete().QueueDeleteImageView(view_);
21
}
22
if (image_ != VK_NULL_HANDLE) {
23
_dbg_assert_(allocation_ != VK_NULL_HANDLE);
24
vulkan_->Delete().QueueDeleteImageAllocation(image_, allocation_);
25
}
26
}
27
28
static bool IsDepthStencilFormat(VkFormat format) {
29
switch (format) {
30
case VK_FORMAT_D16_UNORM:
31
case VK_FORMAT_D16_UNORM_S8_UINT:
32
case VK_FORMAT_D24_UNORM_S8_UINT:
33
case VK_FORMAT_D32_SFLOAT:
34
case VK_FORMAT_D32_SFLOAT_S8_UINT:
35
return true;
36
default:
37
return false;
38
}
39
}
40
41
bool VulkanTexture::CreateDirect(int w, int h, int depth, int numMips, VkFormat format, VkImageLayout initialLayout, VkImageUsageFlags usage, VulkanBarrierBatch *barrierBatch, const VkComponentMapping *mapping) {
42
if (w == 0 || h == 0 || numMips == 0) {
43
ERROR_LOG(Log::G3D, "Can't create a zero-size VulkanTexture");
44
return false;
45
}
46
int maxDim = vulkan_->GetPhysicalDeviceProperties(0).properties.limits.maxImageDimension2D;
47
if (w > maxDim || h > maxDim) {
48
ERROR_LOG(Log::G3D, "Can't create a texture this large");
49
return false;
50
}
51
52
Wipe();
53
54
width_ = (int16_t)w;
55
height_ = (int16_t)h;
56
depth_ = (int16_t)depth;
57
numMips_ = (int16_t)numMips;
58
format_ = format;
59
60
VkImageAspectFlags aspect = IsDepthStencilFormat(format) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
61
62
VkImageCreateInfo image_create_info{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
63
image_create_info.imageType = depth > 1 ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D;
64
image_create_info.format = format_;
65
image_create_info.extent.width = width_;
66
image_create_info.extent.height = height_;
67
image_create_info.extent.depth = depth;
68
image_create_info.mipLevels = numMips;
69
image_create_info.arrayLayers = 1;
70
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
71
image_create_info.flags = 0;
72
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
73
image_create_info.usage = usage;
74
if (initialLayout == VK_IMAGE_LAYOUT_PREINITIALIZED) {
75
image_create_info.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
76
} else {
77
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
78
}
79
80
// The graphics debugger always "needs" TRANSFER_SRC but in practice doesn't matter -
81
// unless validation is on. So let's only force it on when being validated, for now.
82
if (vulkan_->GetInitFlags() & VulkanInitFlags::VALIDATE) {
83
image_create_info.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
84
}
85
VmaAllocationCreateInfo allocCreateInfo{};
86
allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
87
VmaAllocationInfo allocInfo{};
88
VkResult res = vmaCreateImage(vulkan_->Allocator(), &image_create_info, &allocCreateInfo, &image_, &allocation_, &allocInfo);
89
if (res != VK_SUCCESS) {
90
ERROR_LOG(Log::G3D, "vmaCreateImage failed: %s. Destroying image.", VulkanResultToString(res));
91
_dbg_assert_msg_(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS, "%d", (int)res);
92
view_ = VK_NULL_HANDLE;
93
image_ = VK_NULL_HANDLE;
94
allocation_ = VK_NULL_HANDLE;
95
return false;
96
}
97
98
// Apply the tag
99
vulkan_->SetDebugName(image_, VK_OBJECT_TYPE_IMAGE, tag_);
100
101
// Write a command to transition the image to the requested layout, if it's not already that layout.
102
// TODO: We may generate mipmaps right after, so can't add to the end of frame batch. Well actually depending
103
// on the amount of mips we probably sometimes can..
104
105
if (initialLayout != VK_IMAGE_LAYOUT_UNDEFINED && initialLayout != VK_IMAGE_LAYOUT_PREINITIALIZED) {
106
VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
107
VkAccessFlagBits dstAccessFlags = (VkAccessFlagBits)0;
108
switch (initialLayout) {
109
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
110
dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
111
dstAccessFlags = VK_ACCESS_TRANSFER_WRITE_BIT;
112
break;
113
case VK_IMAGE_LAYOUT_GENERAL:
114
// We use this initial layout when we're about to write to the image using a compute shader, only.
115
dstStage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
116
dstAccessFlags = VK_ACCESS_SHADER_READ_BIT;
117
break;
118
default:
119
// If you planned to use UploadMip, you want VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL. After the
120
// upload, you can transition using EndCreate.
121
_assert_(false);
122
break;
123
}
124
barrierBatch->TransitionImage(image_, 0, numMips, 1, VK_IMAGE_ASPECT_COLOR_BIT,
125
VK_IMAGE_LAYOUT_UNDEFINED, initialLayout,
126
0, dstAccessFlags,
127
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, dstStage);
128
}
129
130
// Create the view while we're at it.
131
VkImageViewCreateInfo view_info{ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
132
view_info.image = image_;
133
view_info.viewType = depth > 1 ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D;
134
view_info.format = format_;
135
if (mapping) {
136
view_info.components = *mapping;
137
} else {
138
view_info.components = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY };
139
}
140
view_info.subresourceRange.aspectMask = aspect;
141
view_info.subresourceRange.baseMipLevel = 0;
142
view_info.subresourceRange.levelCount = numMips;
143
view_info.subresourceRange.baseArrayLayer = 0;
144
view_info.subresourceRange.layerCount = 1;
145
146
res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &view_);
147
if (res != VK_SUCCESS) {
148
ERROR_LOG(Log::G3D, "vkCreateImageView failed: %s. Destroying image.", VulkanResultToString(res));
149
_assert_msg_(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS, "%d", (int)res);
150
vmaDestroyImage(vulkan_->Allocator(), image_, allocation_);
151
view_ = VK_NULL_HANDLE;
152
image_ = VK_NULL_HANDLE;
153
allocation_ = VK_NULL_HANDLE;
154
return false;
155
}
156
vulkan_->SetDebugName(view_, VK_OBJECT_TYPE_IMAGE_VIEW, tag_);
157
158
// Additionally, create an array view, but only if it's a 2D texture.
159
if (view_info.viewType == VK_IMAGE_VIEW_TYPE_2D) {
160
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
161
res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &arrayView_);
162
// Assume that if the above view creation succeeded, so will this.
163
_assert_msg_(res == VK_SUCCESS, "View creation failed: %d", (int)res);
164
vulkan_->SetDebugName(arrayView_, VK_OBJECT_TYPE_IMAGE_VIEW, tag_);
165
}
166
167
return true;
168
}
169
170
void VulkanTexture::CopyBufferToMipLevel(VkCommandBuffer cmd, TextureCopyBatch *copyBatch, int mip, int mipWidth, int mipHeight, int depthLayer, VkBuffer buffer, uint32_t offset, size_t rowLength) {
171
VkBufferImageCopy &copy_region = copyBatch->copies.push_uninitialized();
172
copy_region.bufferOffset = offset;
173
copy_region.bufferRowLength = (uint32_t)rowLength;
174
copy_region.bufferImageHeight = 0; // 2D
175
copy_region.imageOffset.x = 0;
176
copy_region.imageOffset.y = 0;
177
copy_region.imageOffset.z = depthLayer;
178
copy_region.imageExtent.width = mipWidth;
179
copy_region.imageExtent.height = mipHeight;
180
copy_region.imageExtent.depth = 1;
181
copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
182
copy_region.imageSubresource.mipLevel = mip;
183
copy_region.imageSubresource.baseArrayLayer = 0;
184
copy_region.imageSubresource.layerCount = 1;
185
186
_dbg_assert_(mip < numMips_);
187
188
if (!copyBatch->buffer) {
189
copyBatch->buffer = buffer;
190
} else if (copyBatch->buffer != buffer) {
191
// Need to flush the batch if this image isn't from the same buffer as the previous ones.
192
FinishCopyBatch(cmd, copyBatch);
193
copyBatch->buffer = buffer;
194
}
195
}
196
197
void VulkanTexture::FinishCopyBatch(VkCommandBuffer cmd, TextureCopyBatch *copyBatch) {
198
if (!copyBatch->copies.empty()) {
199
vkCmdCopyBufferToImage(cmd, copyBatch->buffer, image_, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (uint32_t)copyBatch->copies.size(), copyBatch->copies.data());
200
copyBatch->copies.clear();
201
}
202
}
203
204
void VulkanTexture::ClearMip(VkCommandBuffer cmd, int mip, uint32_t value) {
205
// Must be in TRANSFER_DST mode.
206
VkClearColorValue clearVal;
207
for (int i = 0; i < 4; i++) {
208
clearVal.float32[i] = ((value >> (i * 8)) & 0xFF) / 255.0f;
209
}
210
VkImageSubresourceRange range{};
211
range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
212
range.layerCount = 1;
213
range.baseMipLevel = mip;
214
range.levelCount = 1;
215
vkCmdClearColorImage(cmd, image_, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clearVal, 1, &range);
216
}
217
218
// Low-quality mipmap generation by bilinear blit, but works okay.
219
void VulkanTexture::GenerateMips(VkCommandBuffer cmd, int firstMipToGenerate, bool fromCompute) {
220
_assert_msg_(firstMipToGenerate > 0, "Cannot generate the first level");
221
_assert_msg_(firstMipToGenerate < numMips_, "Can't generate levels beyond storage");
222
223
VulkanBarrierBatch batch;
224
// Transition the pre-set levels to GENERAL.
225
226
VkImageMemoryBarrier *barrier = batch.Add(image_,
227
fromCompute ? VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT : VK_PIPELINE_STAGE_TRANSFER_BIT,
228
VK_PIPELINE_STAGE_TRANSFER_BIT, 0);
229
barrier->oldLayout = fromCompute ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
230
barrier->newLayout = VK_IMAGE_LAYOUT_GENERAL;
231
barrier->srcAccessMask = fromCompute ? VK_ACCESS_SHADER_WRITE_BIT : VK_ACCESS_TRANSFER_WRITE_BIT;
232
barrier->dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
233
barrier->subresourceRange.levelCount = firstMipToGenerate;
234
235
barrier = batch.Add(image_,
236
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
237
VK_PIPELINE_STAGE_TRANSFER_BIT, 0);
238
barrier->subresourceRange.baseMipLevel = firstMipToGenerate;
239
barrier->subresourceRange.levelCount = numMips_ - firstMipToGenerate;
240
barrier->oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
241
barrier->newLayout = VK_IMAGE_LAYOUT_GENERAL;
242
barrier->srcAccessMask = 0;
243
barrier->dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
244
245
batch.Flush(cmd);
246
247
// Now we can blit and barrier the whole pipeline.
248
for (int mip = firstMipToGenerate; mip < numMips_; mip++) {
249
VkImageBlit blit{};
250
blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
251
blit.srcSubresource.layerCount = 1;
252
blit.srcSubresource.mipLevel = mip - 1;
253
blit.srcOffsets[1].x = std::max(width_ >> (mip - 1), 1);
254
blit.srcOffsets[1].y = std::max(height_ >> (mip - 1), 1);
255
blit.srcOffsets[1].z = 1;
256
257
blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
258
blit.dstSubresource.layerCount = 1;
259
blit.dstSubresource.mipLevel = mip;
260
blit.dstOffsets[1].x = std::max(width_ >> mip, 1);
261
blit.dstOffsets[1].y = std::max(height_ >> mip, 1);
262
blit.dstOffsets[1].z = 1;
263
264
// TODO: We could do better with the image transitions - would be enough with one per level
265
// for the memory barrier, then one final one for the whole stack when done. This function
266
// currently doesn't have a global enough view, though.
267
// We should also coalesce barriers across multiple texture uploads in a frame and all kinds of other stuff, but...
268
269
vkCmdBlitImage(cmd, image_, VK_IMAGE_LAYOUT_GENERAL, image_, VK_IMAGE_LAYOUT_GENERAL, 1, &blit, VK_FILTER_LINEAR);
270
271
barrier = batch.Add(image_, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0);
272
barrier->subresourceRange.baseMipLevel = mip;
273
barrier->oldLayout = VK_IMAGE_LAYOUT_GENERAL;
274
barrier->newLayout = VK_IMAGE_LAYOUT_GENERAL;
275
barrier->srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
276
barrier->dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
277
batch.Flush(cmd);
278
}
279
}
280
281
void VulkanTexture::EndCreate(VkCommandBuffer cmd, bool vertexTexture, VkPipelineStageFlags prevStage, VkImageLayout layout) {
282
VulkanBarrierBatch batch;
283
VkImageMemoryBarrier *barrier = batch.Add(image_, prevStage, vertexTexture ? VK_PIPELINE_STAGE_VERTEX_SHADER_BIT : VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0);
284
barrier->subresourceRange.levelCount = numMips_;
285
barrier->oldLayout = layout;
286
barrier->newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
287
barrier->srcAccessMask = prevStage == VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT ? VK_ACCESS_SHADER_WRITE_BIT : VK_ACCESS_TRANSFER_WRITE_BIT;
288
barrier->dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
289
batch.Flush(cmd);
290
}
291
292
void VulkanTexture::PrepareForTransferDst(VkCommandBuffer cmd, int levels) {
293
VulkanBarrierBatch batch;
294
VkImageMemoryBarrier *barrier = batch.Add(image_, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0);
295
barrier->subresourceRange.levelCount = levels;
296
barrier->srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
297
barrier->dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
298
barrier->oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
299
barrier->newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
300
batch.Flush(cmd);
301
}
302
303
void VulkanTexture::RestoreAfterTransferDst(int levels, VulkanBarrierBatch *barriers) {
304
VkImageMemoryBarrier *barrier = barriers->Add(image_, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0);
305
barrier->subresourceRange.levelCount = levels;
306
barrier->srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
307
barrier->dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
308
barrier->oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
309
barrier->newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
310
}
311
312
VkImageView VulkanTexture::CreateViewForMip(int mip) {
313
VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
314
view_info.image = image_;
315
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
316
view_info.format = format_;
317
view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
318
view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
319
view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
320
view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
321
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
322
view_info.subresourceRange.baseMipLevel = mip;
323
view_info.subresourceRange.levelCount = 1;
324
view_info.subresourceRange.baseArrayLayer = 0;
325
view_info.subresourceRange.layerCount = 1;
326
VkImageView view;
327
VkResult res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &view);
328
vulkan_->SetDebugName(view, VK_OBJECT_TYPE_IMAGE_VIEW, "mipview");
329
_assert_(res == VK_SUCCESS);
330
return view;
331
}
332
333
void VulkanTexture::Destroy() {
334
if (view_ != VK_NULL_HANDLE) {
335
vulkan_->Delete().QueueDeleteImageView(view_);
336
}
337
if (arrayView_ != VK_NULL_HANDLE) {
338
vulkan_->Delete().QueueDeleteImageView(arrayView_);
339
}
340
if (image_ != VK_NULL_HANDLE) {
341
_dbg_assert_(allocation_ != VK_NULL_HANDLE);
342
vulkan_->Delete().QueueDeleteImageAllocation(image_, allocation_);
343
}
344
}
345
346