mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-20 17:59:08 +02:00
- Fixes #21739 With the DarkStalkers hack, SoftGPU skips the game's stretch blit and stretches the source image to the screen itself. Libretro always presents software rendering at 480x272, so that stretch happened at 1x and the frontend then scaled up a blurry image. Now libretro gets the 384x224 source as is, with the aspect the game would have stretched it to, and switches back to the full frame when the game stops using the stretch. GL and D3D11 read the frame from a corner of their larger framebuffer. RetroArch's Vulkan driver samples the whole image the core hands it, so the Vulkan presentation images are resized to the frame. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
177 lines
6.1 KiB
C++
177 lines
6.1 KiB
C++
#include "libretro/LibretroVulkanPresentation.h"
|
|
|
|
#include "Common/GPU/Vulkan/VulkanContext.h"
|
|
|
|
using namespace PPSSPP_VK;
|
|
|
|
LibretroVulkanPresentation::LibretroVulkanPresentation(retro_hw_render_interface_vulkan *vulkan, VkFormat format, VkExtent2D extent)
|
|
: vulkan_(vulkan), format_(format), extent_(extent), requestedExtent_(PackExtent(extent)) {
|
|
}
|
|
|
|
uint32_t LibretroVulkanPresentation::ImageCountFromMask(uint32_t mask) {
|
|
uint32_t count = 0;
|
|
while (mask) {
|
|
count++;
|
|
mask >>= 1;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
bool LibretroVulkanPresentation::IsValidImageIndex(uint32_t imageIndex) const {
|
|
return imageIndex < images_.size() && imageIndex < 32 && (syncIndexMask_ & (1u << imageIndex)) != 0;
|
|
}
|
|
|
|
bool LibretroVulkanPresentation::Create(VulkanContext *context) {
|
|
dedicatedAllocation_ = context->Extensions().KHR_dedicated_allocation;
|
|
|
|
syncIndexMask_ = vulkan_->get_sync_index_mask(vulkan_->handle);
|
|
uint32_t count = ImageCountFromMask(syncIndexMask_);
|
|
auto fail = [this, context]() {
|
|
Destroy(context);
|
|
return false;
|
|
};
|
|
|
|
VkDevice device = context->GetDevice();
|
|
images_.resize(count);
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
Image &img = images_[i];
|
|
|
|
VkImageCreateInfo info{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
|
|
info.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
|
info.imageType = VK_IMAGE_TYPE_2D;
|
|
info.format = format_;
|
|
info.extent = { extent_.width, extent_.height, 1 };
|
|
info.mipLevels = 1;
|
|
info.arrayLayers = 1;
|
|
info.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
|
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
if (vkCreateImage(device, &info, nullptr, &img.image) != VK_SUCCESS) {
|
|
return fail();
|
|
}
|
|
|
|
VkMemoryRequirements memreq;
|
|
vkGetImageMemoryRequirements(device, img.image, &memreq);
|
|
|
|
VkMemoryAllocateInfo alloc{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
|
|
alloc.allocationSize = memreq.size;
|
|
|
|
VkMemoryDedicatedAllocateInfoKHR dedicated{ VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR };
|
|
if (dedicatedAllocation_) {
|
|
alloc.pNext = &dedicated;
|
|
dedicated.image = img.image;
|
|
}
|
|
|
|
if (!context->MemoryTypeFromProperties(memreq.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &alloc.memoryTypeIndex)) {
|
|
return fail();
|
|
}
|
|
if (vkAllocateMemory(device, &alloc, nullptr, &img.memory) != VK_SUCCESS) {
|
|
return fail();
|
|
}
|
|
if (vkBindImageMemory(device, img.image, img.memory, 0) != VK_SUCCESS) {
|
|
return fail();
|
|
}
|
|
|
|
img.retroImage.create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
img.retroImage.create_info.image = img.image;
|
|
img.retroImage.create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
img.retroImage.create_info.format = format_;
|
|
img.retroImage.create_info.components = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY };
|
|
img.retroImage.create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
img.retroImage.create_info.subresourceRange.layerCount = 1;
|
|
img.retroImage.create_info.subresourceRange.levelCount = 1;
|
|
if (vkCreateImageView(device, &img.retroImage.create_info, nullptr, &img.retroImage.image_view) != VK_SUCCESS) {
|
|
return fail();
|
|
}
|
|
img.retroImage.image_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void LibretroVulkanPresentation::Destroy(VulkanContext *context) {
|
|
VkDevice device = context->GetDevice();
|
|
for (Image &img : images_) {
|
|
if (img.retroImage.image_view) {
|
|
vkDestroyImageView(device, img.retroImage.image_view, nullptr);
|
|
}
|
|
if (img.image) {
|
|
vkDestroyImage(device, img.image, nullptr);
|
|
}
|
|
if (img.memory) {
|
|
vkFreeMemory(device, img.memory, nullptr);
|
|
}
|
|
}
|
|
images_.clear();
|
|
syncIndexMask_ = 0;
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
presentPending_ = false;
|
|
condVar_.notify_all();
|
|
}
|
|
|
|
bool LibretroVulkanPresentation::NeedsRecreate() const {
|
|
return vulkan_->get_sync_index_mask(vulkan_->handle) != syncIndexMask_ || requestedExtent_ != PackExtent(extent_);
|
|
}
|
|
|
|
bool LibretroVulkanPresentation::Recreate(VulkanContext *context) {
|
|
Destroy(context);
|
|
const uint32_t requested = requestedExtent_;
|
|
extent_ = { requested >> 16, requested & 0xFFFF };
|
|
return Create(context);
|
|
}
|
|
|
|
VkResult LibretroVulkanPresentation::AcquireNextImage(VulkanContext *vulkan, VkSemaphore signalSemaphore, uint32_t *imageIndex) {
|
|
// Unlike a real swapchain, RetroArch doesn't signal signalSemaphore for us here - PrepareSubmit()
|
|
// strips any wait on it before the real vkQueueSubmit, matching the old hack's behavior.
|
|
if (vulkan_->get_sync_index_mask(vulkan_->handle) != syncIndexMask_) {
|
|
return VK_ERROR_OUT_OF_DATE_KHR;
|
|
}
|
|
vulkan_->wait_sync_index(vulkan_->handle);
|
|
*imageIndex = vulkan_->get_sync_index(vulkan_->handle);
|
|
if (!IsValidImageIndex(*imageIndex)) {
|
|
return VK_ERROR_OUT_OF_DATE_KHR;
|
|
}
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VkResult LibretroVulkanPresentation::QueuePresent(VulkanContext *vulkan, VkQueue queue, uint32_t imageIndex, VkSemaphore waitSemaphore) {
|
|
if (vulkan_->get_sync_index_mask(vulkan_->handle) != syncIndexMask_ || !IsValidImageIndex(imageIndex)) {
|
|
return VK_ERROR_OUT_OF_DATE_KHR;
|
|
}
|
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
vulkan_->set_image(vulkan_->handle, &images_[imageIndex].retroImage, 0, nullptr, vulkan_->queue_index);
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
void LibretroVulkanPresentation::BeginPresent() {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
presentPending_ = true;
|
|
}
|
|
|
|
void LibretroVulkanPresentation::EndPresent() {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
presentPending_ = false;
|
|
condVar_.notify_all();
|
|
}
|
|
|
|
void LibretroVulkanPresentation::LockQueue() {
|
|
vulkan_->lock_queue(vulkan_->handle);
|
|
}
|
|
|
|
void LibretroVulkanPresentation::UnlockQueue() {
|
|
vulkan_->unlock_queue(vulkan_->handle);
|
|
}
|
|
|
|
void LibretroVulkanPresentation::PrepareSubmit(VkSubmitInfo &submitInfo) {
|
|
submitInfo.waitSemaphoreCount = 0;
|
|
submitInfo.pWaitSemaphores = nullptr;
|
|
submitInfo.signalSemaphoreCount = 0;
|
|
submitInfo.pSignalSemaphores = nullptr;
|
|
}
|
|
|
|
void LibretroVulkanPresentation::WaitForPresentation() {
|
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
condVar_.wait(lock, [this] { return !presentPending_; });
|
|
}
|