diff --git a/Common/Vulkan/VulkanContext.cpp b/Common/Vulkan/VulkanContext.cpp index 8854970e9d..12993524ef 100644 --- a/Common/Vulkan/VulkanContext.cpp +++ b/Common/Vulkan/VulkanContext.cpp @@ -547,7 +547,7 @@ VkResult VulkanContext::CreateDevice() { return VK_ERROR_INITIALIZATION_FAILED; } - VkDeviceQueueCreateInfo queue_info = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO }; + VkDeviceQueueCreateInfo queue_info{ VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO }; float queue_priorities[1] = { 1.0f }; queue_info.queueCount = 1; queue_info.pQueuePriorities = queue_priorities; @@ -563,7 +563,7 @@ VkResult VulkanContext::CreateDevice() { deviceExtensionsLookup_.DEDICATED_ALLOCATION = EnableDeviceExtension(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); - VkDeviceCreateInfo device_info { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; + VkDeviceCreateInfo device_info{ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; device_info.queueCreateInfoCount = 1; device_info.pQueueCreateInfos = &queue_info; device_info.enabledLayerCount = (uint32_t)device_layer_names_.size(); diff --git a/Common/Vulkan/VulkanImage.cpp b/Common/Vulkan/VulkanImage.cpp index 143ad16332..fa5969ad3e 100644 --- a/Common/Vulkan/VulkanImage.cpp +++ b/Common/Vulkan/VulkanImage.cpp @@ -2,204 +2,6 @@ #include "Common/Vulkan/VulkanMemory.h" #include "Common/Log.h" -VkResult VulkanTexture::Create(int w, int h, VkFormat format) { - tex_width = w; - tex_height = h; - format_ = format; - - VkFormatProperties formatProps; - vkGetPhysicalDeviceFormatProperties(vulkan_->GetPhysicalDevice(), format, &formatProps); - return VK_SUCCESS; -} - -void VulkanTexture::CreateMappableImage() { - // If we already have a mappableImage, forget it. - if (mappableImage) { - vulkan_->Delete().QueueDeleteImage(mappableImage); - } - if (mappableMemory) { - vulkan_->Delete().QueueDeleteDeviceMemory(mappableMemory); - } - - VkImageCreateInfo image_create_info{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - image_create_info.imageType = VK_IMAGE_TYPE_2D; - image_create_info.format = format_; - image_create_info.extent.width = tex_width; - image_create_info.extent.height = tex_height; - image_create_info.extent.depth = 1; - image_create_info.mipLevels = 1; - image_create_info.arrayLayers = 1; - image_create_info.samples = VK_SAMPLE_COUNT_1_BIT; - image_create_info.tiling = VK_IMAGE_TILING_LINEAR; - image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - image_create_info.queueFamilyIndexCount = 0; - image_create_info.pQueueFamilyIndices = NULL; - image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - image_create_info.flags = 0; - image_create_info.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; - - VkMemoryAllocateInfo mem_alloc{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; - mem_alloc.allocationSize = 0; - mem_alloc.memoryTypeIndex = 0; - - // Create a mappable image. It will be the texture if linear images are ok to be textures - // or it will be the staging image if they are not. - VkResult res = vkCreateImage(vulkan_->GetDevice(), &image_create_info, NULL, &mappableImage); - assert(res == VK_SUCCESS); - - vkGetImageMemoryRequirements(vulkan_->GetDevice(), mappableImage, &mem_reqs); - assert(res == VK_SUCCESS); - - mem_alloc.allocationSize = mem_reqs.size; - - // Find the memory type that is host mappable. - bool pass = vulkan_->MemoryTypeFromProperties(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &mem_alloc.memoryTypeIndex); - assert(pass); - - res = vkAllocateMemory(vulkan_->GetDevice(), &mem_alloc, NULL, &mappableMemory); - assert(res == VK_SUCCESS); - - res = vkBindImageMemory(vulkan_->GetDevice(), mappableImage, mappableMemory, 0); - assert(res == VK_SUCCESS); -} - -uint8_t *VulkanTexture::Lock(int level, int *rowPitch) { - CreateMappableImage(); - - VkImageSubresource subres = {}; - subres.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subres.mipLevel = 0; - subres.arrayLayer = 0; - - VkSubresourceLayout layout; - void *data; - - // Get the subresource layout so we know what the row pitch is - vkGetImageSubresourceLayout(vulkan_->GetDevice(), mappableImage, &subres, &layout); - VkResult res = vkMapMemory(vulkan_->GetDevice(), mappableMemory, layout.offset, layout.size, 0, &data); - assert(res == VK_SUCCESS); - - *rowPitch = (int)layout.rowPitch; - return (uint8_t *)data; -} - -void VulkanTexture::Unlock(VkCommandBuffer cmd) { - vkUnmapMemory(vulkan_->GetDevice(), mappableMemory); - - // if we already have an image, queue it for destruction and forget it. - Wipe(); - - { // Shrink the diff by not unindenting. If you make major changes, remove this. - VkImageCreateInfo image_create_info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - image_create_info.imageType = VK_IMAGE_TYPE_2D; - image_create_info.format = format_; - image_create_info.extent.width = tex_width; - image_create_info.extent.height = tex_height; - image_create_info.extent.depth = 1; - image_create_info.mipLevels = 1; - image_create_info.arrayLayers = 1; - image_create_info.samples = VK_SAMPLE_COUNT_1_BIT; - image_create_info.queueFamilyIndexCount = 0; - image_create_info.pQueueFamilyIndices = NULL; - image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - image_create_info.flags = 0; - // The mappable image cannot be our texture, so create an optimally tiled image and blit to it - image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL; - image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - - VkResult res = vkCreateImage(vulkan_->GetDevice(), &image_create_info, NULL, &image); - assert(res == VK_SUCCESS); - - vkGetImageMemoryRequirements(vulkan_->GetDevice(), image, &mem_reqs); - - VkMemoryAllocateInfo mem_alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; - mem_alloc.memoryTypeIndex = 0; - mem_alloc.allocationSize = mem_reqs.size; - - if (allocator_) { - offset_ = allocator_->Allocate(mem_reqs, &mem); - } else { - offset_ = 0; - // 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); - assert(res == VK_SUCCESS); - } - - res = vkBindImageMemory(vulkan_->GetDevice(), image, mem, offset_); - assert(res == VK_SUCCESS); - - // Since we're going to blit from the mappable image, set its layout to SOURCE_OPTIMAL - TransitionImageLayout2(cmd, mappableImage, 0, 1, - VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); - - TransitionImageLayout2(cmd, image, 0, 1, - VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, VK_ACCESS_TRANSFER_WRITE_BIT); - - VkImageCopy copy_region; - copy_region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - copy_region.srcSubresource.mipLevel = 0; - copy_region.srcSubresource.baseArrayLayer = 0; - copy_region.srcSubresource.layerCount = 1; - copy_region.srcOffset.x = 0; - copy_region.srcOffset.y = 0; - copy_region.srcOffset.z = 0; - copy_region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - copy_region.dstSubresource.mipLevel = 0; - copy_region.dstSubresource.baseArrayLayer = 0; - copy_region.dstSubresource.layerCount = 1; - copy_region.dstOffset.x = 0; - copy_region.dstOffset.y = 0; - copy_region.dstOffset.z = 0; - copy_region.extent.width = tex_width; - copy_region.extent.height = tex_height; - copy_region.extent.depth = 1; - - // Put the copy command into the command buffer - vkCmdCopyImage(cmd, - mappableImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, ©_region); - - assert(res == VK_SUCCESS); - - // Set the layout for the texture image from DESTINATION_OPTIMAL to SHADER_READ_ONLY - TransitionImageLayout2(cmd, image, 0, 1, - VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); - - // Then drop the temporary mappable image - although should not be necessary... - vulkan_->Delete().QueueDeleteImage(mappableImage); - vulkan_->Delete().QueueDeleteDeviceMemory(mappableMemory); - } - - 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_R; - view_info.components.g = VK_COMPONENT_SWIZZLE_G; - view_info.components.b = VK_COMPONENT_SWIZZLE_B; - view_info.components.a = VK_COMPONENT_SWIZZLE_A; - view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - view_info.subresourceRange.baseMipLevel = 0; - view_info.subresourceRange.levelCount = 1; - view_info.subresourceRange.baseArrayLayer = 0; - view_info.subresourceRange.layerCount = 1; - VkResult res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &view); - assert(res == VK_SUCCESS); -} - void VulkanTexture::Wipe() { if (image) { vulkan_->Delete().QueueDeleteImage(image); @@ -238,7 +40,7 @@ bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, int w, int h, int numMips, VkImageAspectFlags aspect = IsDepthStencilFormat(format) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; - VkImageCreateInfo image_create_info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + VkImageCreateInfo image_create_info{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; image_create_info.imageType = VK_IMAGE_TYPE_2D; image_create_info.format = format_; image_create_info.extent.width = tex_width; @@ -262,6 +64,7 @@ bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, int w, int h, int numMips, return false; } + VkMemoryRequirements mem_reqs{}; vkGetImageMemoryRequirements(vulkan_->GetDevice(), image, &mem_reqs); if (allocator_) { @@ -270,7 +73,7 @@ bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, int w, int h, int numMips, return false; } } else { - VkMemoryAllocateInfo mem_alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + VkMemoryAllocateInfo mem_alloc{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; mem_alloc.memoryTypeIndex = 0; mem_alloc.allocationSize = mem_reqs.size; @@ -312,7 +115,7 @@ bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, int w, int h, int numMips, } // Create the view while we're at it. - VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; + 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_; @@ -339,7 +142,7 @@ bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, int w, int h, int numMips, } void VulkanTexture::UploadMip(VkCommandBuffer cmd, int mip, int mipWidth, int mipHeight, VkBuffer buffer, uint32_t offset, size_t rowLength) { - VkBufferImageCopy copy_region = {}; + VkBufferImageCopy copy_region{}; copy_region.bufferOffset = offset; copy_region.bufferRowLength = (uint32_t)rowLength; copy_region.bufferImageHeight = 0; // 2D @@ -399,15 +202,9 @@ void VulkanTexture::Destroy() { vulkan_->Delete().QueueDeleteImageView(view); } if (image != VK_NULL_HANDLE) { - if (mappableImage == image) { - mappableImage = VK_NULL_HANDLE; - } vulkan_->Delete().QueueDeleteImage(image); } if (mem != VK_NULL_HANDLE && !allocator_) { - if (mappableMemory == mem) { - mappableMemory = VK_NULL_HANDLE; - } vulkan_->Delete().QueueDeleteDeviceMemory(mem); } else if (mem != VK_NULL_HANDLE) { allocator_->Free(mem, offset_); diff --git a/Common/Vulkan/VulkanImage.h b/Common/Vulkan/VulkanImage.h index 2b8486c975..df2a45edd1 100644 --- a/Common/Vulkan/VulkanImage.h +++ b/Common/Vulkan/VulkanImage.h @@ -11,23 +11,12 @@ public: VulkanTexture(VulkanContext *vulkan, VulkanDeviceAllocator *allocator) : vulkan_(vulkan), image(VK_NULL_HANDLE), mem(VK_NULL_HANDLE), view(VK_NULL_HANDLE), tex_width(0), tex_height(0), numMips_(1), format_(VK_FORMAT_UNDEFINED), - mappableImage(VK_NULL_HANDLE), mappableMemory(VK_NULL_HANDLE), allocator_(allocator), offset_(0) { - memset(&mem_reqs, 0, sizeof(mem_reqs)); } ~VulkanTexture() { Destroy(); } - // Simple usage - no cleverness, no mipmaps. - // Always call Create, Lock, Unlock. Unlock performs the upload if necessary. - // Can later Lock and Unlock again. This cannot change the format. Create cannot - // be called a second time without recreating the texture object until Destroy has - // been called. - VkResult Create(int w, int h, VkFormat format); - uint8_t *Lock(int level, int *rowPitch); - void Unlock(VkCommandBuffer cmd); - // 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. @@ -50,18 +39,14 @@ public: VkFormat GetFormat() const { return format_; } private: - void CreateMappableImage(); void Wipe(); VulkanContext *vulkan_; VkImage image; - VkDeviceMemory mem; VkImageView view; + VkDeviceMemory mem; int32_t tex_width, tex_height, numMips_; VkFormat format_; - VkImage mappableImage; - VkDeviceMemory mappableMemory; - VkMemoryRequirements mem_reqs; VulkanDeviceAllocator *allocator_; size_t offset_; }; diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index 4460a57492..1e7929202e 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -36,7 +36,7 @@ VulkanPushBuffer::~VulkanPushBuffer() { bool VulkanPushBuffer::AddBuffer() { BufInfo info; - VkBufferCreateInfo b = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + VkBufferCreateInfo b{ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; b.size = size_; b.flags = 0; b.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; @@ -56,7 +56,7 @@ bool VulkanPushBuffer::AddBuffer() { // TODO: We really should use memoryTypeIndex here.. // Okay, that's the buffer. Now let's allocate some memory for it. - VkMemoryAllocateInfo alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + VkMemoryAllocateInfo alloc{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; // TODO: Should check here that memoryTypeIndex_ matches reqs.memoryTypeBits. alloc.memoryTypeIndex = memoryTypeIndex_; alloc.allocationSize = reqs.size; @@ -145,7 +145,7 @@ void VulkanPushBuffer::Unmap() { assert(writePtr_); /* // Should not need this since we use coherent memory. - VkMappedMemoryRange range = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE }; + VkMappedMemoryRange range{ VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE }; range.offset = 0; range.size = offset_; range.memory = buffers_[buf_].deviceMemory; @@ -380,7 +380,7 @@ bool VulkanDeviceAllocator::AllocateSlab(VkDeviceSize minBytes) { minSlabSize_ <<= 1; } - VkMemoryAllocateInfo alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + VkMemoryAllocateInfo alloc{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; alloc.allocationSize = minSlabSize_; alloc.memoryTypeIndex = memoryTypeIndex_; diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 56a393f447..122b50ddaa 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -95,42 +95,36 @@ DrawEngineVulkan::DrawEngineVulkan(VulkanContext *vulkan, Draw::DrawContext *dra void DrawEngineVulkan::InitDeviceObjects() { // All resources we need for PSP drawing. Usually only bindings 0 and 2-4 are populated. - VkDescriptorSetLayoutBinding bindings[6]; + VkDescriptorSetLayoutBinding bindings[6]{}; bindings[0].descriptorCount = 1; - bindings[0].pImmutableSamplers = nullptr; bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; bindings[0].binding = DRAW_BINDING_TEXTURE; bindings[1].descriptorCount = 1; - bindings[1].pImmutableSamplers = nullptr; bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; bindings[1].binding = DRAW_BINDING_2ND_TEXTURE; bindings[2].descriptorCount = 1; - bindings[2].pImmutableSamplers = nullptr; bindings[2].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; bindings[2].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; bindings[2].binding = DRAW_BINDING_DYNUBO_BASE; bindings[3].descriptorCount = 1; - bindings[3].pImmutableSamplers = nullptr; bindings[3].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; bindings[3].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; bindings[3].binding = DRAW_BINDING_DYNUBO_LIGHT; bindings[4].descriptorCount = 1; - bindings[4].pImmutableSamplers = nullptr; bindings[4].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; bindings[4].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; bindings[4].binding = DRAW_BINDING_DYNUBO_BONE; // Used only for hardware tessellation. bindings[5].descriptorCount = 1; - bindings[5].pImmutableSamplers = nullptr; bindings[5].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; bindings[5].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; bindings[5].binding = DRAW_BINDING_TESS_STORAGE_BUF; VkDevice device = vulkan_->GetDevice(); - VkDescriptorSetLayoutCreateInfo dsl = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; + VkDescriptorSetLayoutCreateInfo dsl{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; dsl.bindingCount = ARRAY_SIZE(bindings); dsl.pBindings = bindings; VkResult res = vkCreateDescriptorSetLayout(device, &dsl, nullptr, &descriptorSetLayout_); @@ -145,7 +139,7 @@ void DrawEngineVulkan::InitDeviceObjects() { frame_[i].pushIndex = new VulkanPushBuffer(vulkan_, 1 * 1024 * 1024); } - VkPipelineLayoutCreateInfo pl = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; + VkPipelineLayoutCreateInfo pl{ VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; pl.pPushConstantRanges = nullptr; pl.pushConstantRangeCount = 0; pl.setLayoutCount = 1; @@ -154,7 +148,7 @@ void DrawEngineVulkan::InitDeviceObjects() { res = vkCreatePipelineLayout(device, &pl, nullptr, &pipelineLayout_); assert(VK_SUCCESS == res); - VkSamplerCreateInfo samp = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; + VkSamplerCreateInfo samp{ VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; samp.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samp.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samp.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; @@ -424,6 +418,36 @@ void DrawEngineVulkan::SetLineWidth(float lineWidth) { pipelineManager_->SetLineWidth(lineWidth); } +VkResult DrawEngineVulkan::RecreateDescriptorPool(FrameData &frame, int newSize) { + // Reallocate this desc pool larger, and "wipe" the cache. We might lose a tiny bit of descriptor set reuse but + // only for this frame. + if (frame.descPool) { + DEBUG_LOG(G3D, "Reallocating desc pool from %d to %d", frame.descPoolSize, newSize); + vulkan_->Delete().QueueDeleteDescriptorPool(frame.descPool); + frame.descSets.Clear(); + frame.descCount = 0; + } + frame.descPoolSize = newSize; + + VkDescriptorPoolSize dpTypes[3]; + dpTypes[0].descriptorCount = frame.descPoolSize * 3; + dpTypes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + dpTypes[1].descriptorCount = frame.descPoolSize * 2; // Don't use these for tess anymore, need max two per set. + dpTypes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + dpTypes[2].descriptorCount = frame.descPoolSize; + dpTypes[2].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + + VkDescriptorPoolCreateInfo dp{ VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; + dp.flags = 0; // Don't want to mess around with individually freeing these. + // We zap the whole pool every few frames. + dp.maxSets = frame.descPoolSize; + dp.pPoolSizes = dpTypes; + dp.poolSizeCount = ARRAY_SIZE(dpTypes); + + VkResult res = vkCreateDescriptorPool(vulkan_->GetDevice(), &dp, nullptr, &frame.descPool); + return res; +} + VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView, VkSampler sampler, VkBuffer base, VkBuffer light, VkBuffer bone, bool tess) { DescriptorSetKey key; key.imageView_ = imageView; @@ -445,47 +469,32 @@ VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView } if (!frame.descPool || frame.descPoolSize < frame.descCount + 1) { - // Reallocate this desc pool larger, and "wipe" the cache. We might lose a tiny bit of descriptor set reuse but - // only for this frame. - if (frame.descPool) { - DEBUG_LOG(G3D, "Reallocating desc pool from %d to %d", frame.descPoolSize, frame.descPoolSize * 2); - vulkan_->Delete().QueueDeleteDescriptorPool(frame.descPool); - frame.descSets.Clear(); - frame.descCount = 0; - } - frame.descPoolSize *= 2; - - VkDescriptorPoolSize dpTypes[3]; - dpTypes[0].descriptorCount = frame.descPoolSize * 3; - dpTypes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; - dpTypes[1].descriptorCount = frame.descPoolSize * 2; // Don't use these for tess anymore, need max two per set. - dpTypes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - dpTypes[2].descriptorCount = frame.descPoolSize; - dpTypes[2].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - - VkDescriptorPoolCreateInfo dp = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; - dp.pNext = nullptr; - dp.flags = 0; // Don't want to mess around with individually freeing these. - // We zap the whole pool every few frames. - dp.maxSets = frame.descPoolSize; - dp.pPoolSizes = dpTypes; - dp.poolSizeCount = ARRAY_SIZE(dpTypes); - - VkResult res = vkCreateDescriptorPool(vulkan_->GetDevice(), &dp, nullptr, &frame.descPool); + VkResult res = RecreateDescriptorPool(frame, frame.descPoolSize * 2); assert(res == VK_SUCCESS); } - // Didn't find one in the frame descriptor set cache, let's make a new one. // We wipe the cache on every frame. VkDescriptorSet desc; - VkDescriptorSetAllocateInfo descAlloc = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; + VkDescriptorSetAllocateInfo descAlloc{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; descAlloc.pNext = nullptr; descAlloc.pSetLayouts = &descriptorSetLayout_; descAlloc.descriptorPool = frame.descPool; descAlloc.descriptorSetCount = 1; VkResult result = vkAllocateDescriptorSets(vulkan_->GetDevice(), &descAlloc, &desc); + + if (result == VK_ERROR_FRAGMENTED_POOL || result < 0) { + // There seems to have been a spec revision. Here we should apparently recreate the descriptor pool, + // so let's do that. See https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkAllocateDescriptorSets.html + // Fragmentation shouldn't really happen though since we wipe the pool every frame.. + VkResult res = RecreateDescriptorPool(frame, frame.descPoolSize); + _assert_msg_(G3D, res == VK_SUCCESS, "Ran out of descriptor space (frag?) and failed to recreate a descriptor pool. sz=%d res=%d", (int)frame.descSets.size(), (int)res); + descAlloc.descriptorPool = frame.descPool; // Need to update this pointer since we have allocated a new one. + result = vkAllocateDescriptorSets(vulkan_->GetDevice(), &descAlloc, &desc); + _assert_msg_(G3D, result == VK_SUCCESS, "Ran out of descriptor space (frag?) and failed to allocate after recreating a descriptor pool. res=%d", (int)result); + } + // Even in release mode, this is bad. _assert_msg_(G3D, result == VK_SUCCESS, "Ran out of descriptor space in pool. sz=%d res=%d", (int)frame.descSets.size(), (int)result); diff --git a/GPU/Vulkan/DrawEngineVulkan.h b/GPU/Vulkan/DrawEngineVulkan.h index 673cc4b4f4..15890cefde 100644 --- a/GPU/Vulkan/DrawEngineVulkan.h +++ b/GPU/Vulkan/DrawEngineVulkan.h @@ -195,6 +195,7 @@ private: void DestroyDeviceObjects(); void DecodeVertsToPushBuffer(VulkanPushBuffer *push, uint32_t *bindOffset, VkBuffer *vkbuf); + VkResult RecreateDescriptorPool(FrameData &frame, int newSize); void DoFlush(); void UpdateUBOs(FrameData *frame); diff --git a/GPU/Vulkan/PipelineManagerVulkan.cpp b/GPU/Vulkan/PipelineManagerVulkan.cpp index 4dcd4b6235..eabd8a1827 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.cpp +++ b/GPU/Vulkan/PipelineManagerVulkan.cpp @@ -133,7 +133,7 @@ static VulkanPipeline *CreateVulkanPipeline(VkDevice device, VkPipelineCache pip } blend0.colorWriteMask = key.colorWriteMask; - VkPipelineColorBlendStateCreateInfo cbs = { VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO }; + VkPipelineColorBlendStateCreateInfo cbs{ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO }; cbs.flags = 0; cbs.pAttachments = &blend0; cbs.attachmentCount = 1; @@ -143,7 +143,7 @@ static VulkanPipeline *CreateVulkanPipeline(VkDevice device, VkPipelineCache pip else cbs.logicOp = VK_LOGIC_OP_COPY; - VkPipelineDepthStencilStateCreateInfo dss = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO }; + VkPipelineDepthStencilStateCreateInfo dss{ VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO }; dss.depthBoundsTestEnable = false; dss.stencilTestEnable = key.stencilTestEnable; if (key.stencilTestEnable) { @@ -175,12 +175,12 @@ static VulkanPipeline *CreateVulkanPipeline(VkDevice device, VkPipelineCache pip dynamicStates[numDyn++] = VK_DYNAMIC_STATE_STENCIL_REFERENCE; } - VkPipelineDynamicStateCreateInfo ds = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO }; + VkPipelineDynamicStateCreateInfo ds{ VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO }; ds.flags = 0; ds.pDynamicStates = dynamicStates; ds.dynamicStateCount = numDyn; - VkPipelineRasterizationStateCreateInfo rs = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; + VkPipelineRasterizationStateCreateInfo rs{ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; rs.flags = 0; rs.depthBiasEnable = false; rs.cullMode = key.cullMode; @@ -190,7 +190,7 @@ static VulkanPipeline *CreateVulkanPipeline(VkDevice device, VkPipelineCache pip rs.polygonMode = VK_POLYGON_MODE_FILL; rs.depthClampEnable = key.depthClampEnable; - VkPipelineMultisampleStateCreateInfo ms = { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO }; + VkPipelineMultisampleStateCreateInfo ms{ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO }; ms.pSampleMask = nullptr; ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; @@ -215,7 +215,7 @@ static VulkanPipeline *CreateVulkanPipeline(VkDevice device, VkPipelineCache pip return nullptr; } - VkPipelineInputAssemblyStateCreateInfo inputAssembly = { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO }; + VkPipelineInputAssemblyStateCreateInfo inputAssembly{ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO }; inputAssembly.flags = 0; inputAssembly.topology = (VkPrimitiveTopology)key.topology; inputAssembly.primitiveRestartEnable = false; @@ -238,21 +238,21 @@ static VulkanPipeline *CreateVulkanPipeline(VkDevice device, VkPipelineCache pip ibd.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; ibd.stride = vertexStride; - VkPipelineVertexInputStateCreateInfo vis = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; + VkPipelineVertexInputStateCreateInfo vis{ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; vis.flags = 0; vis.vertexBindingDescriptionCount = 1; vis.pVertexBindingDescriptions = &ibd; vis.vertexAttributeDescriptionCount = attributeCount; vis.pVertexAttributeDescriptions = attrs; - VkPipelineViewportStateCreateInfo views = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO }; + VkPipelineViewportStateCreateInfo views{ VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO }; views.flags = 0; views.viewportCount = 1; views.scissorCount = 1; views.pViewports = nullptr; // dynamic views.pScissors = nullptr; // dynamic - VkGraphicsPipelineCreateInfo pipe = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; + VkGraphicsPipelineCreateInfo pipe{ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; pipe.flags = 0; pipe.stageCount = 2; pipe.pStages = ss; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index d570aa8ce5..7c95f1388d 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -123,8 +123,6 @@ static UI::Theme ui_theme; #include "android/android-ndk-profiler/prof.h" #endif -std::unique_ptr uiTexture; - ScreenManager *screenManager; std::string config_filename; @@ -622,16 +620,6 @@ bool NativeInitGraphics(GraphicsContext *graphicsContext) { UIThemeInit(); - uiTexture = CreateTextureFromFile(g_draw, "ui_atlas.zim", ImageFileType::ZIM); - if (!uiTexture) { - PanicAlert("Failed to load ui_atlas.zim.\n\nPlace it in the directory \"assets\" under your PPSSPP directory."); - ELOG("Failed to load ui_atlas.zim"); -#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) - UINT ExitCode = 0; - ExitProcess(ExitCode); -#endif - } - uiContext = new UIContext(); uiContext->theme = &ui_theme; @@ -714,8 +702,6 @@ void NativeShutdownGraphics() { UIBackgroundShutdown(); - uiTexture.reset(nullptr); - delete uiContext; uiContext = nullptr; @@ -804,10 +790,6 @@ void RenderOverlays(UIContext *dc, void *userdata) { void NativeRender(GraphicsContext *graphicsContext) { g_GameManager.Update(); - // If uitexture gets reloaded, make sure we use the latest one. - // Not sure this happens anymore now that we tear down all graphics on app switches... - uiContext->FrameSetup(uiTexture->GetTexture()); - float xres = dp_xres; float yres = dp_yres; diff --git a/UI/TextureUtil.h b/UI/TextureUtil.h index 28a28b69d3..c71b1e39ba 100644 --- a/UI/TextureUtil.h +++ b/UI/TextureUtil.h @@ -3,7 +3,6 @@ #include #include "thin3d/thin3d.h" -#include "Core/Config.h" enum ImageFileType { PNG, diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index 0fa38ecdf1..58cee3ee00 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -301,9 +301,9 @@ struct DescriptorSetKey { class VKTexture : public Texture { public: - VKTexture(VulkanContext *vulkan, VkCommandBuffer cmd, const TextureDesc &desc, VulkanDeviceAllocator *alloc) + VKTexture(VulkanContext *vulkan, VkCommandBuffer cmd, VulkanPushBuffer *pushBuffer, const TextureDesc &desc, VulkanDeviceAllocator *alloc) : vulkan_(vulkan), mipLevels_(desc.mipLevels), format_(desc.format) { - bool result = Create(cmd, desc, alloc); + bool result = Create(cmd, pushBuffer, desc, alloc); assert(result); } @@ -314,9 +314,7 @@ public: VkImageView GetImageView() { return vkTex_->GetImageView(); } private: - void SetImageData(VkCommandBuffer cmd, int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data); - - bool Create(VkCommandBuffer cmd, const TextureDesc &desc, VulkanDeviceAllocator *alloc); + bool Create(VkCommandBuffer cmd, VulkanPushBuffer *pushBuffer, const TextureDesc &desc, VulkanDeviceAllocator *alloc); void Destroy() { if (vkTex_) { @@ -650,20 +648,45 @@ enum class TextureState { PENDING_DESTRUCTION, }; -bool VKTexture::Create(VkCommandBuffer cmd, const TextureDesc &desc, VulkanDeviceAllocator *alloc) { +bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushBuffer *push, const TextureDesc &desc, VulkanDeviceAllocator *alloc) { // Zero-sized textures not allowed. assert(desc.width * desc.height * desc.depth > 0); + assert(push); format_ = desc.format; mipLevels_ = desc.mipLevels; width_ = desc.width; height_ = desc.height; depth_ = desc.depth; vkTex_ = new VulkanTexture(vulkan_, alloc); + VkFormat vulkanFormat = DataFormatToVulkan(format_); + int stride = desc.width * (int)DataFormatSizeInBytes(format_); + int bpp = GetBpp(vulkanFormat); + int bytesPerPixel = bpp / 8; + int usageBits = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + if (mipLevels_ > (int)desc.initData.size()) { + // Gonna have to generate some, which requires TRANSFER_SRC + usageBits |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + } + vkTex_->CreateDirect(cmd, width_, height_, mipLevels_, vulkanFormat, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, usageBits); if (desc.initData.size()) { - for (int i = 0; i < (int)desc.initData.size(); i++) { - this->SetImageData(cmd, 0, 0, 0, width_, height_, depth_, i, 0, desc.initData[i]); + int w = width_; + int h = height_; + int i; + for (i = 0; i < (int)desc.initData.size(); i++) { + uint32_t offset; + VkBuffer buf; + size_t size = w * h * bytesPerPixel; + offset = push->PushAligned((const void *)desc.initData[i], size, 16, &buf); + vkTex_->UploadMip(cmd, i, w, h, buf, offset, w); + w = (w + 1) / 2; + h = (h + 1) / 2; + } + // Generate the rest of the mips automatically. + for (; i < mipLevels_; i++) { + vkTex_->GenerateMip(cmd, i); } } + vkTex_->EndCreate(cmd, false); return true; } @@ -703,13 +726,13 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit) dpTypes[1].descriptorCount = 200; dpTypes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - VkDescriptorPoolCreateInfo dp = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; + VkDescriptorPoolCreateInfo dp{ VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; dp.flags = 0; // Don't want to mess around with individually freeing these, let's go dynamic each frame. dp.maxSets = 200; // 200 textures per frame should be enough for the UI... dp.pPoolSizes = dpTypes; dp.poolSizeCount = ARRAY_SIZE(dpTypes); - VkCommandPoolCreateInfo p = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; + VkCommandPoolCreateInfo p{ VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; p.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; p.queueFamilyIndex = vulkan->GetGraphicsQueueFamilyIndex(); @@ -994,23 +1017,7 @@ InputLayout *VKContext::CreateInputLayout(const InputLayoutDesc &desc) { } Texture *VKContext::CreateTexture(const TextureDesc &desc) { - return new VKTexture(vulkan_, renderManager_.GetInitCmd(), desc, allocator_); -} - -void VKTexture::SetImageData(VkCommandBuffer cmd, int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data) { - VkFormat vulkanFormat = DataFormatToVulkan(format_); - if (stride == 0) { - stride = width * (int)DataFormatSizeInBytes(format_); - } - int bpp = GetBpp(vulkanFormat); - int bytesPerPixel = bpp / 8; - vkTex_->Create(width, height, vulkanFormat); - int rowPitch; - uint8_t *dstData = vkTex_->Lock(0, &rowPitch); - for (int y = 0; y < height; y++) { - memcpy(dstData + rowPitch * y, data + stride * y, width * bytesPerPixel); - } - vkTex_->Unlock(cmd); + return new VKTexture(vulkan_, renderManager_.GetInitCmd(), push_, desc, allocator_); } static inline void CopySide(VkStencilOpState &dest, const StencilSide &src) { diff --git a/ext/native/ui/ui_context.cpp b/ext/native/ui/ui_context.cpp index 671765a552..6ad2d78c8c 100644 --- a/ext/native/ui/ui_context.cpp +++ b/ext/native/ui/ui_context.cpp @@ -6,6 +6,8 @@ #include "gfx_es2/draw_buffer.h" #include "gfx_es2/draw_text.h" +#include "Common/Log.h" + UIContext::UIContext() { fontStyle_ = new UI::FontStyle(); bounds_ = Bounds(0, 0, dp_xres, dp_yres); @@ -28,13 +30,19 @@ void UIContext::Init(Draw::DrawContext *thin3d, Draw::Pipeline *uipipe, Draw::Pi textDrawer_ = TextDrawer::Create(thin3d); // May return nullptr if no implementation is available for this platform. } -void UIContext::FrameSetup(Draw::Texture *uiTexture) { - uitexture_ = uiTexture; +void UIContext::BeginFrame() { + if (!uitexture_) { + uitexture_ = CreateTextureFromFile(draw_, "ui_atlas.zim", ImageFileType::ZIM); + if (!uitexture_) { + PanicAlert("Failed to load ui_atlas.zim.\n\nPlace it in the directory \"assets\" under your PPSSPP directory."); + FLOG("Failed to load ui_atlas.zim"); + } + } } void UIContext::Begin() { draw_->BindSamplerStates(0, 1, &sampler_); - draw_->BindTexture(0, uitexture_); + draw_->BindTexture(0, uitexture_->GetTexture()); ActivateTopScissor(); UIBegin(ui_pipeline_); } @@ -45,7 +53,7 @@ void UIContext::BeginNoTex() { } void UIContext::RebindTexture() const { - draw_->BindTexture(0, uitexture_); + draw_->BindTexture(0, uitexture_->GetTexture()); } void UIContext::Flush() { diff --git a/ext/native/ui/ui_context.h b/ext/native/ui/ui_context.h index 59468abde7..3b6c4d7e43 100644 --- a/ext/native/ui/ui_context.h +++ b/ext/native/ui/ui_context.h @@ -6,6 +6,7 @@ #include "math/geom2d.h" #include "math/lin/vec3.h" #include "gfx/texture_atlas.h" +#include "UI/TextureUtil.h" // Everything you need to draw a UI collected into a single unit that can be passed around. // Everything forward declared so this header is safe everywhere. @@ -46,7 +47,7 @@ public: void Init(Draw::DrawContext *thin3d, Draw::Pipeline *uipipe, Draw::Pipeline *uipipenotex, DrawBuffer *uidrawbuffer, DrawBuffer *uidrawbufferTop); - void FrameSetup(Draw::Texture *uiTexture); + void BeginFrame(); void Begin(); void BeginNoTex(); @@ -102,7 +103,7 @@ private: Draw::SamplerState *sampler_; Draw::Pipeline *ui_pipeline_ = nullptr; Draw::Pipeline *ui_pipeline_notex_ = nullptr; - Draw::Texture *uitexture_ = nullptr; + std::unique_ptr uitexture_; DrawBuffer *uidrawbuffer_ = nullptr; DrawBuffer *uidrawbufferTop_ = nullptr; diff --git a/ext/native/ui/ui_screen.cpp b/ext/native/ui/ui_screen.cpp index 26e12b8b4f..092eacec1c 100644 --- a/ext/native/ui/ui_screen.cpp +++ b/ext/native/ui/ui_screen.cpp @@ -68,6 +68,7 @@ void UIScreen::preRender() { return; } draw->BeginFrame(); + screenManager()->getUIContext()->BeginFrame(); // Bind and clear the back buffer draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 });