More integration, use VMA in VulkanTexture

This commit is contained in:
Henrik Rydgård 2021-11-21 23:38:14 +01:00
parent 8f9ce03a8d
commit 1b1e585a35
10 changed files with 71 additions and 115 deletions

View file

@ -437,6 +437,7 @@
<ClInclude Include="GPU\ShaderWriter.h" />
<ClInclude Include="GPU\thin3d.h" />
<ClInclude Include="GPU\thin3d_create.h" />
<ClInclude Include="GPU\Vulkan\VulkanAlloc.h" />
<ClInclude Include="GPU\Vulkan\VulkanContext.h" />
<ClInclude Include="GPU\Vulkan\VulkanDebug.h" />
<ClInclude Include="GPU\Vulkan\VulkanImage.h" />

View file

@ -409,6 +409,9 @@
<ClInclude Include="..\ext\vma\vk_mem_alloc.h">
<Filter>ext\vma</Filter>
</ClInclude>
<ClInclude Include="GPU\Vulkan\VulkanAlloc.h">
<Filter>GPU\Vulkan</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="ABI.cpp" />

View file

@ -0,0 +1,17 @@
#pragma once
#ifdef USE_CRT_DBG
#undef new
#endif
#include "Common/GPU/Vulkan/VulkanLoader.h"
using namespace PPSSPP_VK;
#undef VK_NO_PROTOTYPES
#include "ext/vma/vk_mem_alloc.h"
#define VK_NO_PROTOTYPES
#ifdef USE_CRT_DBG
#define new DBG_NEW
#endif

View file

@ -283,7 +283,7 @@ void VulkanContext::DestroyInstance() {
void VulkanContext::BeginFrame() {
FrameData *frame = &frame_[curFrame_];
// Process pending deletes.
frame->deleteList.PerformDeletes(device_);
frame->deleteList.PerformDeletes(device_, allocator_);
}
void VulkanContext::EndFrame() {
@ -665,9 +665,7 @@ VkResult VulkanContext::CreateDevice() {
allocatorInfo.physicalDevice = physical_devices_[physical_device_];
allocatorInfo.device = device_;
allocatorInfo.instance = instance_;
VmaAllocator allocator;
vmaCreateAllocator(&allocatorInfo, &allocator);
vmaCreateAllocator(&allocatorInfo, &allocator_);
return res;
}
@ -1099,9 +1097,9 @@ VkFence VulkanContext::CreateFence(bool presignalled) {
void VulkanContext::PerformPendingDeletes() {
for (int i = 0; i < ARRAY_SIZE(frame_); i++) {
frame_[i].deleteList.PerformDeletes(device_);
frame_[i].deleteList.PerformDeletes(device_, allocator_);
}
Delete().PerformDeletes(device_);
Delete().PerformDeletes(device_, allocator_);
}
void VulkanContext::DestroyDevice() {
@ -1343,7 +1341,7 @@ void VulkanDeleteList::Take(VulkanDeleteList &del) {
del.callbacks_.clear();
}
void VulkanDeleteList::PerformDeletes(VkDevice device) {
void VulkanDeleteList::PerformDeletes(VkDevice device, VmaAllocator allocator) {
for (auto &callback : callbacks_) {
callback.func(callback.userdata);
}
@ -1372,6 +1370,10 @@ void VulkanDeleteList::PerformDeletes(VkDevice device) {
vkDestroyImage(device, image, nullptr);
}
images_.clear();
for (auto &imageWithAlloc : imagesWithAllocs_) {
vmaDestroyImage(allocator, imageWithAlloc.image, imageWithAlloc.alloc);
}
imagesWithAllocs_.clear();
for (auto &imageView : imageViews_) {
vkDestroyImageView(device, imageView, nullptr);
}

View file

@ -8,6 +8,7 @@
#include "Common/Log.h"
#include "Common/GPU/Vulkan/VulkanLoader.h"
#include "Common/GPU/Vulkan/VulkanDebug.h"
#include "Common/GPU/Vulkan/VulkanAlloc.h"
enum {
VULKAN_FLAG_VALIDATE = 1,
@ -27,6 +28,7 @@ enum {
};
VK_DEFINE_HANDLE(VmaAllocator);
VK_DEFINE_HANDLE(VmaAllocation);
std::string VulkanVendorString(uint32_t vendorId);
@ -62,6 +64,11 @@ struct VulkanPhysicalDeviceInfo {
// This is a bit repetitive...
class VulkanDeleteList {
struct ImageWithAlloc {
VkImage image;
VmaAllocation alloc;
};
struct Callback {
explicit Callback(void(*f)(void *userdata), void *u)
: func(f), userdata(u) {
@ -79,6 +86,7 @@ public:
void QueueDeleteBuffer(VkBuffer &buffer) { _dbg_assert_(buffer != VK_NULL_HANDLE); buffers_.push_back(buffer); buffer = VK_NULL_HANDLE; }
void QueueDeleteBufferView(VkBufferView &bufferView) { _dbg_assert_(bufferView != VK_NULL_HANDLE); bufferViews_.push_back(bufferView); bufferView = VK_NULL_HANDLE; }
void QueueDeleteImage(VkImage &image) { _dbg_assert_(image != VK_NULL_HANDLE); images_.push_back(image); image = VK_NULL_HANDLE; }
void QueueDeleteImageAllocation(VkImage &image, VmaAllocation &alloc) { _dbg_assert_(image != VK_NULL_HANDLE && alloc != VK_NULL_HANDLE); imagesWithAllocs_.push_back(ImageWithAlloc{ image, alloc }); image = VK_NULL_HANDLE; alloc = VK_NULL_HANDLE; }
void QueueDeleteImageView(VkImageView &imageView) { _dbg_assert_(imageView != VK_NULL_HANDLE); imageViews_.push_back(imageView); imageView = VK_NULL_HANDLE; }
void QueueDeleteDeviceMemory(VkDeviceMemory &deviceMemory) { _dbg_assert_(deviceMemory != VK_NULL_HANDLE); deviceMemory_.push_back(deviceMemory); deviceMemory = VK_NULL_HANDLE; }
void QueueDeleteSampler(VkSampler &sampler) { _dbg_assert_(sampler != VK_NULL_HANDLE); samplers_.push_back(sampler); sampler = VK_NULL_HANDLE; }
@ -91,7 +99,7 @@ public:
void QueueCallback(void(*func)(void *userdata), void *userdata) { callbacks_.push_back(Callback(func, userdata)); }
void Take(VulkanDeleteList &del);
void PerformDeletes(VkDevice device);
void PerformDeletes(VkDevice device, VmaAllocator allocator);
private:
std::vector<VkCommandPool> cmdPools_;
@ -100,6 +108,7 @@ private:
std::vector<VkBuffer> buffers_;
std::vector<VkBufferView> bufferViews_;
std::vector<VkImage> images_;
std::vector<ImageWithAlloc> imagesWithAllocs_;
std::vector<VkImageView> imageViews_;
std::vector<VkDeviceMemory> deviceMemory_;
std::vector<VkSampler> samplers_;
@ -279,6 +288,10 @@ public:
void GetImageMemoryRequirements(VkImage image, VkMemoryRequirements *mem_reqs, bool *dedicatedAllocation);
VmaAllocator Allocator() const {
return allocator_;
}
private:
bool ChooseQueue();

View file

@ -1,24 +1,19 @@
#include <algorithm>
#include "Common/Log.h"
#include "Common/GPU/Vulkan/VulkanAlloc.h"
#include "Common/GPU/Vulkan/VulkanImage.h"
#include "Common/GPU/Vulkan/VulkanMemory.h"
using namespace PPSSPP_VK;
void VulkanTexture::Wipe() {
if (image_) {
vulkan_->Delete().QueueDeleteImage(image_);
}
if (view_) {
if (view_ != VK_NULL_HANDLE) {
vulkan_->Delete().QueueDeleteImageView(view_);
}
if (mem_ && !allocator_) {
vulkan_->Delete().QueueDeleteDeviceMemory(mem_);
} else if (mem_) {
allocator_->Free(mem_, offset_);
mem_ = VK_NULL_HANDLE;
if (image_ != VK_NULL_HANDLE) {
_dbg_assert_(allocation_ != VK_NULL_HANDLE);
vulkan_->Delete().QueueDeleteImageAllocation(image_, allocation_);
}
}
@ -35,7 +30,7 @@ static bool IsDepthStencilFormat(VkFormat format) {
}
}
bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, VulkanDeviceAllocator *allocator, int w, int h, int numMips, VkFormat format, VkImageLayout initialLayout, VkImageUsageFlags usage, const VkComponentMapping *mapping) {
bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, int w, int h, int numMips, VkFormat format, VkImageLayout initialLayout, VkImageUsageFlags usage, const VkComponentMapping *mapping) {
if (w == 0 || h == 0 || numMips == 0) {
ERROR_LOG(G3D, "Can't create a zero-size VulkanTexture");
return false;
@ -73,63 +68,13 @@ bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, VulkanDeviceAllocator *all
if (vulkan_->GetFlags() & VULKAN_FLAG_VALIDATE) {
image_create_info.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
VkResult res = vkCreateImage(vulkan_->GetDevice(), &image_create_info, NULL, &image_);
if (res != VK_SUCCESS) {
_assert_(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS);
ERROR_LOG(G3D, "vkCreateImage failed: %s", VulkanResultToString(res));
return false;
}
VmaAllocationCreateInfo allocCreateInfo{};
allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
VmaAllocationInfo allocInfo{};
VkResult res = vmaCreateImage(vulkan_->Allocator(), &image_create_info, &allocCreateInfo, &image_, &allocation_, &allocInfo);
// Apply the tag
vulkan_ ->SetDebugName(image_, VK_OBJECT_TYPE_IMAGE, tag_.c_str());
VkMemoryRequirements mem_reqs{};
bool dedicatedAllocation = false;
vulkan_->GetImageMemoryRequirements(image_, &mem_reqs, &dedicatedAllocation);
if (allocator && !dedicatedAllocation) {
allocator_ = allocator;
// ok to use the tag like this, because the lifetime of the VulkanImage exceeds that of the allocation.
offset_ = allocator_->Allocate(mem_reqs, &mem_, Tag().c_str());
if (offset_ == VulkanDeviceAllocator::ALLOCATE_FAILED) {
ERROR_LOG(G3D, "Image memory allocation failed (mem_reqs.size=%d, typebits=%08x", (int)mem_reqs.size, (int)mem_reqs.memoryTypeBits);
// Destructor will take care of the image.
return false;
}
} else {
VkMemoryAllocateInfo mem_alloc{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
mem_alloc.memoryTypeIndex = 0;
mem_alloc.allocationSize = mem_reqs.size;
VkMemoryDedicatedAllocateInfoKHR dedicatedAllocateInfo{VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR};
if (dedicatedAllocation) {
dedicatedAllocateInfo.image = image_;
mem_alloc.pNext = &dedicatedAllocateInfo;
}
// Find memory type - don't specify any mapping requirements
bool pass = vulkan_->MemoryTypeFromProperties(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &mem_alloc.memoryTypeIndex);
_assert_(pass);
res = vkAllocateMemory(vulkan_->GetDevice(), &mem_alloc, NULL, &mem_);
if (res != VK_SUCCESS) {
ERROR_LOG(G3D, "vkAllocateMemory failed: %s", VulkanResultToString(res));
_assert_msg_(res != VK_ERROR_TOO_MANY_OBJECTS, "Too many Vulkan memory objects!");
_assert_(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS);
return false;
}
offset_ = 0;
}
res = vkBindImageMemory(vulkan_->GetDevice(), image_, mem_, offset_);
if (res != VK_SUCCESS) {
ERROR_LOG(G3D, "vkBindImageMemory failed: %s", VulkanResultToString(res));
// This leaks the image and memory. Should not really happen though...
_assert_(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS);
return false;
}
vulkan_->SetDebugName(image_, VK_OBJECT_TYPE_IMAGE, tag_.c_str());
// Write a command to transition the image to the requested layout, if it's not already that layout.
if (initialLayout != VK_IMAGE_LAYOUT_UNDEFINED && initialLayout != VK_IMAGE_LAYOUT_PREINITIALIZED) {
@ -268,12 +213,6 @@ void VulkanTexture::EndCreate(VkCommandBuffer cmd, bool vertexTexture, VkPipelin
prevStage == VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT ? VK_ACCESS_SHADER_WRITE_BIT : VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
}
void VulkanTexture::Touch() {
if (allocator_ && mem_ != VK_NULL_HANDLE) {
allocator_->Touch(mem_, offset_);
}
}
VkImageView VulkanTexture::CreateViewForMip(int mip) {
VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
view_info.image = image_;
@ -299,15 +238,7 @@ void VulkanTexture::Destroy() {
vulkan_->Delete().QueueDeleteImageView(view_);
}
if (image_ != VK_NULL_HANDLE) {
vulkan_->Delete().QueueDeleteImage(image_);
}
if (mem_ != VK_NULL_HANDLE) {
if (allocator_) {
allocator_->Free(mem_, offset_);
mem_ = VK_NULL_HANDLE;
allocator_ = nullptr;
} else {
vulkan_->Delete().QueueDeleteDeviceMemory(mem_);
}
_dbg_assert_(allocation_ != VK_NULL_HANDLE);
vulkan_->Delete().QueueDeleteImageAllocation(image_, allocation_);
}
}

View file

@ -4,6 +4,8 @@
class VulkanDeviceAllocator;
VK_DEFINE_HANDLE(VmaAllocation);
// Wrapper around what you need to use a texture.
// ALWAYS use an allocator when calling CreateDirect.
class VulkanTexture {
@ -18,7 +20,7 @@ public:
// Fast uploads from buffer. Mipmaps supported.
// Usage must at least include VK_IMAGE_USAGE_TRANSFER_DST_BIT in order to use UploadMip.
// When using UploadMip, initialLayout should be VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL.
bool CreateDirect(VkCommandBuffer cmd, VulkanDeviceAllocator *allocator, int w, int h, int numMips, VkFormat format, VkImageLayout initialLayout, VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, const VkComponentMapping *mapping = nullptr);
bool CreateDirect(VkCommandBuffer cmd, int w, int h, int numMips, VkFormat format, VkImageLayout initialLayout, VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, const VkComponentMapping *mapping = nullptr);
void ClearMip(VkCommandBuffer cmd, int mip, uint32_t value);
void UploadMip(VkCommandBuffer cmd, int mip, int mipWidth, int mipHeight, VkBuffer buffer, uint32_t offset, size_t rowLength); // rowLength is in pixels
@ -37,7 +39,7 @@ public:
const std::string &Tag() const {
return tag_;
}
void Touch();
void Touch() {}
// Used in image copies, etc.
VkImage GetImage() const { return image_; }
@ -56,12 +58,12 @@ private:
VulkanContext *vulkan_;
VkImage image_ = VK_NULL_HANDLE;
VkImageView view_ = VK_NULL_HANDLE;
VkDeviceMemory mem_ = VK_NULL_HANDLE;
VmaAllocation allocation_;
int32_t width_ = 0;
int32_t height_ = 0;
int32_t numMips_ = 1;
VkFormat format_ = VK_FORMAT_UNDEFINED;
VulkanDeviceAllocator *allocator_ = nullptr; // If set, memory is from this allocator.
size_t offset_ = 0;
std::string tag_;
};

View file

@ -319,7 +319,7 @@ class VKTexture : public Texture {
public:
VKTexture(VulkanContext *vulkan, VkCommandBuffer cmd, VulkanPushBuffer *pushBuffer, const TextureDesc &desc)
: vulkan_(vulkan), mipLevels_(desc.mipLevels), format_(desc.format) {}
bool Create(VkCommandBuffer cmd, VulkanPushBuffer *pushBuffer, const TextureDesc &desc, VulkanDeviceAllocator *alloc);
bool Create(VkCommandBuffer cmd, VulkanPushBuffer *pushBuffer, const TextureDesc &desc);
~VKTexture() {
Destroy();
@ -510,8 +510,6 @@ private:
VulkanRenderManager renderManager_;
VulkanDeviceAllocator *allocator_ = nullptr;
VulkanTexture *nullTexture_ = nullptr;
AutoRef<VKPipeline> curPipeline_;
@ -640,7 +638,7 @@ VulkanTexture *VKContext::GetNullTexture() {
nullTexture_->SetTag("Null");
int w = 8;
int h = 8;
nullTexture_->CreateDirect(cmdInit, allocator_, w, h, 1, VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
nullTexture_->CreateDirect(cmdInit, w, h, 1, VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
uint32_t bindOffset;
VkBuffer bindBuf;
@ -707,7 +705,7 @@ enum class TextureState {
PENDING_DESTRUCTION,
};
bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushBuffer *push, const TextureDesc &desc, VulkanDeviceAllocator *alloc) {
bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushBuffer *push, const TextureDesc &desc) {
// Zero-sized textures not allowed.
_assert_(desc.width * desc.height * desc.depth > 0); // remember to set depth to 1!
if (desc.width * desc.height * desc.depth <= 0) {
@ -733,7 +731,7 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushBuffer *push, const Textur
usageBits |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
if (!vkTex_->CreateDirect(cmd, alloc, width_, height_, mipLevels_, vulkanFormat, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, usageBits)) {
if (!vkTex_->CreateDirect(cmd, width_, height_, mipLevels_, vulkanFormat, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, usageBits)) {
ERROR_LOG(G3D, "Failed to create VulkanTexture: %dx%dx%d fmt %d, %d levels", width_, height_, depth_, (int)vulkanFormat, mipLevels_);
return false;
}
@ -878,19 +876,10 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit)
_assert_(VK_SUCCESS == res);
renderManager_.SetSplitSubmit(splitSubmit);
allocator_ = new VulkanDeviceAllocator(vulkan_, 256 * 1024, 2048 * 1024);
}
VKContext::~VKContext() {
delete nullTexture_;
allocator_->Destroy();
// We have to delete on queue, so this can free its queued deletions.
vulkan_->Delete().QueueCallback([](void *ptr) {
auto allocator = static_cast<VulkanDeviceAllocator *>(ptr);
delete allocator;
}, allocator_);
allocator_ = nullptr;
// This also destroys all descriptor sets.
for (int i = 0; i < VulkanContext::MAX_INFLIGHT_FRAMES; i++) {
frame_[i].descSets_.clear();
@ -912,7 +901,6 @@ void VKContext::BeginFrame() {
// OK, we now know that nothing is reading from this frame's data pushbuffer,
push_->Reset();
push_->Begin(vulkan_);
allocator_->Begin();
frame.descSets_.clear();
VkResult result = vkResetDescriptorPool(device_, frame.descriptorPool, 0);
@ -922,7 +910,6 @@ void VKContext::BeginFrame() {
void VKContext::EndFrame() {
// Stop collecting data in the frame's data pushbuffer.
push_->End();
allocator_->End();
renderManager_.Finish();
@ -1227,7 +1214,7 @@ Texture *VKContext::CreateTexture(const TextureDesc &desc) {
return nullptr;
}
VKTexture *tex = new VKTexture(vulkan_, initCmd, push_, desc);
if (tex->Create(initCmd, push_, desc, allocator_)) {
if (tex->Create(initCmd, push_, desc)) {
return tex;
} else {
ERROR_LOG(G3D, "Failed to create texture");

View file

@ -168,7 +168,7 @@ VulkanTexture *DepalShaderCacheVulkan::GetClutTexture(GEPaletteFormat clutFormat
VulkanTexture *vktex = new VulkanTexture(vulkan);
vktex->SetTag("DepalClut");
VkCommandBuffer cmd = (VkCommandBuffer)draw_->GetNativeObject(Draw::NativeObject::INIT_COMMANDBUFFER);
if (!vktex->CreateDirect(cmd, alloc_, texturePixels, 1, 1, destFormat,
if (!vktex->CreateDirect(cmd, texturePixels, 1, 1, destFormat,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, &componentMapping)) {
ERROR_LOG(G3D, "Failed to create texture for CLUT");
return nullptr;

View file

@ -755,7 +755,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) {
snprintf(texName, sizeof(texName), "tex_%08x_%s", entry->addr, GeTextureFormatToString((GETextureFormat)entry->format, gstate.getClutPaletteFormat()));
image->SetTag(texName);
bool allocSuccess = image->CreateDirect(cmdInit, allocator_, w * scaleFactor, h * scaleFactor, maxLevelToGenerate + 1, actualFmt, imageLayout, usage, mapping);
bool allocSuccess = image->CreateDirect(cmdInit, w * scaleFactor, h * scaleFactor, maxLevelToGenerate + 1, actualFmt, imageLayout, usage, mapping);
if (!allocSuccess && !lowMemoryMode_) {
WARN_LOG_REPORT(G3D, "Texture cache ran out of GPU memory; switching to low memory mode");
lowMemoryMode_ = true;
@ -774,7 +774,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) {
scaleFactor = 1;
actualFmt = dstFmt;
allocSuccess = image->CreateDirect(cmdInit, allocator_, w * scaleFactor, h * scaleFactor, maxLevelToGenerate + 1, actualFmt, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, mapping);
allocSuccess = image->CreateDirect(cmdInit, w * scaleFactor, h * scaleFactor, maxLevelToGenerate + 1, actualFmt, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, mapping);
}
if (!allocSuccess) {