Files
ppsspp/Common/GPU/Vulkan/VulkanImage.cpp
T
Henrik RydgårdandClaude Opus 5.5 e666ab49de Remove leftovers from hardware tessellation
Nothing reads from textures or storage buffers in vertex shaders since
hardware tessellation was removed. Drop what was left for it: the
vertexTexture flag of VulkanTexture::EndCreate (always false), the
STORAGE_BUFFER_VERTEX binding type, GL's maxVertexTextureUnits, and the
tesselationShaderSupported cap, which nothing read (and which was always
false, since the feature was never enabled).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 12:02:10 +02:00

351 lines
15 KiB
C++

#include <algorithm>
#include "Common/Log.h"
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/GPU/Vulkan/VulkanAlloc.h"
#include "Common/GPU/Vulkan/VulkanImage.h"
#include "Common/GPU/Vulkan/VulkanMemory.h"
#include "Common/GPU/Vulkan/VulkanBarrier.h"
#include "Common/StringUtils.h"
using namespace PPSSPP_VK;
VulkanTexture::VulkanTexture(VulkanContext *vulkan, const char *tag)
: vulkan_(vulkan) {
truncate_cpy(tag_, tag);
}
void VulkanTexture::Wipe() {
if (view_ != VK_NULL_HANDLE) {
vulkan_->Delete().QueueDeleteImageView(view_);
}
if (image_ != VK_NULL_HANDLE) {
_dbg_assert_(allocation_ != VK_NULL_HANDLE);
vulkan_->Delete().QueueDeleteImageAllocation(image_, allocation_);
}
}
static bool IsDepthStencilFormat(VkFormat format) {
switch (format) {
case VK_FORMAT_D16_UNORM:
case VK_FORMAT_D16_UNORM_S8_UINT:
case VK_FORMAT_D24_UNORM_S8_UINT:
case VK_FORMAT_D32_SFLOAT:
case VK_FORMAT_D32_SFLOAT_S8_UINT:
return true;
default:
return false;
}
}
bool VulkanTexture::CreateDirect(int w, int h, int depth, int numMips, VkFormat format, VkImageLayout initialLayout, VkImageUsageFlags usage, VulkanBarrierBatch *barrierBatch, const VkComponentMapping *mapping) {
if (w == 0 || h == 0 || numMips == 0) {
ERROR_LOG(Log::G3D, "Can't create a zero-size VulkanTexture (tag: '%s')", tag_);
return false;
}
int maxDim = vulkan_->GetPhysicalDeviceProperties().properties.limits.maxImageDimension2D;
if (w > maxDim || h > maxDim) {
ERROR_LOG(Log::G3D, "Can't create a texture with format %s this large (%d > %d or %d > %d) (tag: '%s')", VulkanFormatToString(format), w, maxDim, h, maxDim, tag_);
return false;
}
_dbg_assert_(format != VK_FORMAT_UNDEFINED);
Wipe();
width_ = (int16_t)w;
height_ = (int16_t)h;
depth_ = (int16_t)depth;
numMips_ = (int16_t)numMips;
format_ = format;
VkImageAspectFlags aspect = IsDepthStencilFormat(format) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
VkImageCreateInfo image_create_info{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
image_create_info.imageType = depth > 1 ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D;
image_create_info.format = format_;
image_create_info.extent.width = width_;
image_create_info.extent.height = height_;
image_create_info.extent.depth = depth;
image_create_info.mipLevels = numMips;
image_create_info.arrayLayers = 1;
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_create_info.flags = 0;
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = usage;
if (initialLayout == VK_IMAGE_LAYOUT_PREINITIALIZED) {
image_create_info.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
} else {
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
}
// The graphics debugger always "needs" TRANSFER_SRC but in practice doesn't matter -
// unless validation is on. So let's only force it on when being validated, for now.
if (vulkan_->GetInitFlags() & VulkanInitFlags::VALIDATE) {
image_create_info.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
VmaAllocationCreateInfo allocCreateInfo{};
allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
VmaAllocationInfo allocInfo{};
VkResult res = vmaCreateImage(vulkan_->Allocator(), &image_create_info, &allocCreateInfo, &image_, &allocation_, &allocInfo);
if (res != VK_SUCCESS) {
ERROR_LOG(Log::G3D, "vmaCreateImage failed: %s. Destroying image (tag: '%s')", VulkanResultToString(res), tag_);
_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);
view_ = VK_NULL_HANDLE;
image_ = VK_NULL_HANDLE;
allocation_ = VK_NULL_HANDLE;
return false;
}
// Apply the tag
vulkan_->SetDebugName(image_, VK_OBJECT_TYPE_IMAGE, tag_);
// Write a command to transition the image to the requested layout, if it's not already that layout.
// TODO: We may generate mipmaps right after, so can't add to the end of frame batch. Well actually depending
// on the amount of mips we probably sometimes can..
if (initialLayout != VK_IMAGE_LAYOUT_UNDEFINED && initialLayout != VK_IMAGE_LAYOUT_PREINITIALIZED) {
VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlagBits dstAccessFlags = (VkAccessFlagBits)0;
switch (initialLayout) {
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
dstAccessFlags = VK_ACCESS_TRANSFER_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_GENERAL:
// We use this initial layout when we're about to write to the image using a compute shader, only.
dstStage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
dstAccessFlags = VK_ACCESS_SHADER_READ_BIT;
break;
default:
// If you planned to use UploadMip, you want VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL. After the
// upload, you can transition using EndCreate.
_assert_(false);
break;
}
barrierBatch->TransitionImage(image_, 0, numMips, 1, VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_UNDEFINED, initialLayout,
0, dstAccessFlags,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, dstStage);
}
// Create the view while we're at it.
VkImageViewCreateInfo view_info{ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
view_info.image = image_;
view_info.viewType = depth > 1 ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D;
view_info.format = format_;
if (mapping) {
view_info.components = *mapping;
} else {
view_info.components = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY };
}
view_info.subresourceRange.aspectMask = aspect;
view_info.subresourceRange.baseMipLevel = 0;
view_info.subresourceRange.levelCount = numMips;
view_info.subresourceRange.baseArrayLayer = 0;
view_info.subresourceRange.layerCount = 1;
res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &view_);
if (res != VK_SUCCESS) {
ERROR_LOG(Log::G3D, "vkCreateImageView failed: %s. Destroying image (tag: '%s')", VulkanResultToString(res), tag_);
_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);
vmaDestroyImage(vulkan_->Allocator(), image_, allocation_);
view_ = VK_NULL_HANDLE;
image_ = VK_NULL_HANDLE;
allocation_ = VK_NULL_HANDLE;
return false;
}
vulkan_->SetDebugName(view_, VK_OBJECT_TYPE_IMAGE_VIEW, tag_);
// Additionally, create an array view, but only if it's a 2D texture.
if (view_info.viewType == VK_IMAGE_VIEW_TYPE_2D) {
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &arrayView_);
// Assume that if the above view creation succeeded, so will this.
_assert_msg_(res == VK_SUCCESS, "View creation failed: %d (tag: '%s')", (int)res, tag_);
vulkan_->SetDebugName(arrayView_, VK_OBJECT_TYPE_IMAGE_VIEW, tag_);
}
return true;
}
void VulkanTexture::CopyBufferToMipLevel(VkCommandBuffer cmd, TextureCopyBatch *copyBatch, int mip, int mipWidth, int mipHeight, int depthLayer, VkBuffer buffer, uint32_t offset, size_t rowLength) {
CopyBufferToMipLevelRegion(cmd, copyBatch, mip, 0, 0, mipWidth, mipHeight, depthLayer, buffer, offset, rowLength);
}
void VulkanTexture::CopyBufferToMipLevelRegion(VkCommandBuffer cmd, TextureCopyBatch *copyBatch, int mip, int x, int y, int w, int h, int depthLayer, VkBuffer buffer, uint32_t offset, size_t rowLength) {
VkBufferImageCopy &copy_region = copyBatch->copies.push_uninitialized();
copy_region.bufferOffset = offset;
copy_region.bufferRowLength = (uint32_t)rowLength;
copy_region.bufferImageHeight = 0; // 2D
copy_region.imageOffset.x = x;
copy_region.imageOffset.y = y;
copy_region.imageOffset.z = depthLayer;
copy_region.imageExtent.width = w;
copy_region.imageExtent.height = h;
copy_region.imageExtent.depth = 1;
copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy_region.imageSubresource.mipLevel = mip;
copy_region.imageSubresource.baseArrayLayer = 0;
copy_region.imageSubresource.layerCount = 1;
_dbg_assert_(mip < numMips_);
if (!copyBatch->buffer) {
copyBatch->buffer = buffer;
} else if (copyBatch->buffer != buffer) {
// Need to flush the batch if this image isn't from the same buffer as the previous ones.
FinishCopyBatch(cmd, copyBatch);
copyBatch->buffer = buffer;
}
}
void VulkanTexture::FinishCopyBatch(VkCommandBuffer cmd, TextureCopyBatch *copyBatch) {
if (!copyBatch->copies.empty()) {
vkCmdCopyBufferToImage(cmd, copyBatch->buffer, image_, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (uint32_t)copyBatch->copies.size(), copyBatch->copies.data());
copyBatch->copies.clear();
}
}
void VulkanTexture::ClearMip(VkCommandBuffer cmd, int mip, uint32_t value) {
// Must be in TRANSFER_DST mode.
VkClearColorValue clearVal;
for (int i = 0; i < 4; i++) {
clearVal.float32[i] = ((value >> (i * 8)) & 0xFF) / 255.0f;
}
VkImageSubresourceRange range{};
range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
range.layerCount = 1;
range.baseMipLevel = mip;
range.levelCount = 1;
vkCmdClearColorImage(cmd, image_, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clearVal, 1, &range);
}
// Low-quality mipmap generation by bilinear blit, but works okay.
void VulkanTexture::GenerateMips(VkCommandBuffer cmd, int firstMipToGenerate, bool fromCompute) {
_assert_msg_(firstMipToGenerate > 0, "Cannot generate the first level");
_assert_msg_(firstMipToGenerate < numMips_, "Can't generate levels beyond storage");
VulkanBarrierBatch batch;
// Transition the pre-set levels to GENERAL.
VkImageMemoryBarrier *barrier = batch.Add(image_,
fromCompute ? VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT : VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0);
barrier->oldLayout = fromCompute ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier->newLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier->srcAccessMask = fromCompute ? VK_ACCESS_SHADER_WRITE_BIT : VK_ACCESS_TRANSFER_WRITE_BIT;
barrier->dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier->subresourceRange.levelCount = firstMipToGenerate;
barrier = batch.Add(image_,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0);
barrier->subresourceRange.baseMipLevel = firstMipToGenerate;
barrier->subresourceRange.levelCount = numMips_ - firstMipToGenerate;
barrier->oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier->newLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier->srcAccessMask = 0;
barrier->dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
batch.Flush(cmd);
// Now we can blit and barrier the whole pipeline.
for (int mip = firstMipToGenerate; mip < numMips_; mip++) {
VkImageBlit blit{};
blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit.srcSubresource.layerCount = 1;
blit.srcSubresource.mipLevel = mip - 1;
blit.srcOffsets[1].x = std::max(width_ >> (mip - 1), 1);
blit.srcOffsets[1].y = std::max(height_ >> (mip - 1), 1);
blit.srcOffsets[1].z = 1;
blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit.dstSubresource.layerCount = 1;
blit.dstSubresource.mipLevel = mip;
blit.dstOffsets[1].x = std::max(width_ >> mip, 1);
blit.dstOffsets[1].y = std::max(height_ >> mip, 1);
blit.dstOffsets[1].z = 1;
// TODO: We could do better with the image transitions - would be enough with one per level
// for the memory barrier, then one final one for the whole stack when done. This function
// currently doesn't have a global enough view, though.
// We should also coalesce barriers across multiple texture uploads in a frame and all kinds of other stuff, but...
vkCmdBlitImage(cmd, image_, VK_IMAGE_LAYOUT_GENERAL, image_, VK_IMAGE_LAYOUT_GENERAL, 1, &blit, VK_FILTER_LINEAR);
barrier = batch.Add(image_, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0);
barrier->subresourceRange.baseMipLevel = mip;
barrier->oldLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier->newLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier->srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier->dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
batch.Flush(cmd);
}
}
void VulkanTexture::EndCreate(VkCommandBuffer cmd, VkPipelineStageFlags prevStage, VkImageLayout layout) {
VulkanBarrierBatch batch;
VkImageMemoryBarrier *barrier = batch.Add(image_, prevStage, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0);
barrier->subresourceRange.levelCount = numMips_;
barrier->oldLayout = layout;
barrier->newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier->srcAccessMask = prevStage == VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT ? VK_ACCESS_SHADER_WRITE_BIT : VK_ACCESS_TRANSFER_WRITE_BIT;
barrier->dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
batch.Flush(cmd);
}
void VulkanTexture::PrepareForTransferDst(VkCommandBuffer cmd, int levels) {
VulkanBarrierBatch batch;
VkImageMemoryBarrier *barrier = batch.Add(image_, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0);
barrier->subresourceRange.levelCount = levels;
barrier->srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
barrier->dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier->oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier->newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
batch.Flush(cmd);
}
void VulkanTexture::RestoreAfterTransferDst(int levels, VulkanBarrierBatch *barriers) {
VkImageMemoryBarrier *barrier = barriers->Add(image_, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0);
barrier->subresourceRange.levelCount = levels;
barrier->srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier->dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
barrier->oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier->newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
VkImageView VulkanTexture::CreateViewForMip(int mip) {
VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
view_info.image = image_;
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.format = format_;
view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
view_info.subresourceRange.baseMipLevel = mip;
view_info.subresourceRange.levelCount = 1;
view_info.subresourceRange.baseArrayLayer = 0;
view_info.subresourceRange.layerCount = 1;
VkImageView view;
VkResult res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &view);
vulkan_->SetDebugName(view, VK_OBJECT_TYPE_IMAGE_VIEW, "mipview");
_assert_(res == VK_SUCCESS);
return view;
}
void VulkanTexture::Destroy() {
if (view_ != VK_NULL_HANDLE) {
vulkan_->Delete().QueueDeleteImageView(view_);
}
if (arrayView_ != VK_NULL_HANDLE) {
vulkan_->Delete().QueueDeleteImageView(arrayView_);
}
if (image_ != VK_NULL_HANDLE) {
_dbg_assert_(allocation_ != VK_NULL_HANDLE);
vulkan_->Delete().QueueDeleteImageAllocation(image_, allocation_);
}
}