Renderer fixes (Splash + Aspect Ratio) (#2645)

* rewrite splash

removed Splash class
rewrite using imgui texture manager
fix crashes & old validation error

* handle games with abnormal aspect ratios
This commit is contained in:
Vinicius Rangel 2025-03-13 13:10:24 -03:00 committed by GitHub
parent 36927a7bbd
commit 5691046dcc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 66 additions and 222 deletions

View file

@ -644,8 +644,6 @@ set(CORE src/core/aerolib/stubs.cpp
src/core/file_format/playgo_chunk.h
src/core/file_format/trp.cpp
src/core/file_format/trp.h
src/core/file_format/splash.h
src/core/file_format/splash.cpp
src/core/file_sys/fs.cpp
src/core/file_sys/fs.h
src/core/loader.cpp

View file

@ -3,6 +3,7 @@
#pragma once
#include <filesystem>
#include <string>
#include <string_view>
@ -69,6 +70,8 @@ class ElfInfo {
u32 raw_firmware_ver = 0;
PSFAttributes psf_attributes{};
std::filesystem::path splash_path{};
public:
static constexpr u32 FW_15 = 0x1500000;
static constexpr u32 FW_16 = 0x1600000;
@ -116,6 +119,10 @@ public:
ASSERT(initialized);
return psf_attributes;
}
[[nodiscard]] const std::filesystem::path& GetSplashPath() const {
return splash_path;
}
};
} // namespace Common

View file

@ -21,8 +21,8 @@
extern std::unique_ptr<Vulkan::Presenter> presenter;
using namespace ImGui;
using namespace Core::Devtools;
using L = Core::Devtools::Layer;
using namespace ::Core::Devtools;
using L = ::Core::Devtools::Layer;
static bool show_simple_fps = false;
static bool visibility_toggled = false;

View file

@ -1,38 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fstream>
#include "common/assert.h"
#include "common/io_file.h"
#include "common/stb.h"
#include "splash.h"
bool Splash::Open(const std::filesystem::path& filepath) {
ASSERT_MSG(filepath.extension().string() == ".png", "Unexpected file format passed");
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
return false;
}
std::vector<u8> png_file{};
const auto png_size = file.GetSize();
png_file.resize(png_size);
file.Seek(0);
file.Read(png_file);
auto* img_mem = stbi_load_from_memory(png_file.data(), png_file.size(),
reinterpret_cast<int*>(&img_info.width),
reinterpret_cast<int*>(&img_info.height),
reinterpret_cast<int*>(&img_info.num_channels), 4);
if (!img_mem) {
return false;
}
const auto img_size = img_info.GetSizeBytes();
img_data.resize(img_size);
std::memcpy(img_data.data(), img_mem, img_size);
stbi_image_free(img_mem);
return true;
}

View file

@ -1,42 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <string>
#include <vector>
#include "common/types.h"
class Splash {
public:
struct ImageInfo {
u32 width;
u32 height;
u32 num_channels;
u32 GetSizeBytes() const {
return width * height * 4; // we always forcing rgba8 for simplicity
}
};
Splash() = default;
~Splash() = default;
bool Open(const std::filesystem::path& filepath);
[[nodiscard]] bool IsLoaded() const {
return img_data.size();
}
const auto& GetImageData() const {
return img_data;
}
ImageInfo GetImageInfo() const {
return img_info;
}
private:
ImageInfo img_info{};
std::vector<u8> img_data{};
};

View file

@ -160,11 +160,8 @@ int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) {
}
void VideoOutDriver::Flip(const Request& req) {
// Whatever the game is rendering show splash if it is active
if (!presenter->ShowSplash(req.frame)) {
// Present the frame.
presenter->Present(req.frame);
}
// Present the frame.
presenter->Present(req.frame);
// Update flip status.
auto* port = req.port;
@ -201,9 +198,6 @@ void VideoOutDriver::Flip(const Request& req) {
}
void VideoOutDriver::DrawBlankFrame() {
if (presenter->ShowSplash(nullptr)) {
return;
}
const auto empty_frame = presenter->PrepareBlankFrame(false);
presenter->Present(empty_frame);
}

View file

@ -24,7 +24,6 @@
#include "common/singleton.h"
#include "common/version.h"
#include "core/file_format/psf.h"
#include "core/file_format/splash.h"
#include "core/file_format/trp.h"
#include "core/file_sys/fs.h"
#include "core/libraries/disc_map/disc_map.h"
@ -185,12 +184,7 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector<std::str
const auto pic1_path = mnt->GetHostPath("/app0/sce_sys/pic1.png");
if (std::filesystem::exists(pic1_path)) {
auto* splash = Common::Singleton<Splash>::Instance();
if (!splash->IsLoaded()) {
if (!splash->Open(pic1_path)) {
LOG_ERROR(Loader, "Game splash: unable to open file");
}
}
game_info.splash_path = pic1_path;
}
game_info.initialized = true;

View file

@ -175,6 +175,7 @@ void WorkerLoop() {
auto texture = Vulkan::UploadTexture(pixels, vk::Format::eR8G8B8A8Unorm, width, height,
width * height * 4 * sizeof(stbi_uc));
stbi_image_free((void*)pixels);
core->upload_data = texture;
core->width = width;

View file

@ -6,7 +6,6 @@
#include "common/singleton.h"
#include "core/debug_state.h"
#include "core/devtools/layer.h"
#include "core/file_format/splash.h"
#include "core/libraries/system/systemservice.h"
#include "imgui/renderer/imgui_core.h"
#include "sdl_window.h"
@ -21,6 +20,8 @@
#include <vk_mem_alloc.h>
#include <imgui.h>
#include "common/elf_info.h"
#include "imgui/renderer/imgui_impl_vulkan.h"
namespace Vulkan {
@ -269,116 +270,10 @@ Frame* Presenter::PrepareLastFrame() {
return frame;
}
bool Presenter::ShowSplash(Frame* frame /*= nullptr*/) {
const auto* splash = Common::Singleton<Splash>::Instance();
if (splash->GetImageData().empty()) {
return false;
}
if (!Libraries::SystemService::IsSplashVisible()) {
return false;
}
draw_scheduler.EndRendering();
const auto cmdbuf = draw_scheduler.CommandBuffer();
if (Config::getVkHostMarkersEnabled()) {
cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{
.pLabelName = "ShowSplash",
});
}
if (!frame) {
if (!splash_img.has_value()) {
VideoCore::ImageInfo info{};
info.pixel_format = vk::Format::eR8G8B8A8Unorm;
info.type = vk::ImageType::e2D;
info.size =
VideoCore::Extent3D{splash->GetImageInfo().width, splash->GetImageInfo().height, 1};
info.pitch = splash->GetImageInfo().width;
info.guest_address = VAddr(splash->GetImageData().data());
info.guest_size = splash->GetImageData().size();
info.mips_layout.emplace_back(splash->GetImageData().size(),
splash->GetImageInfo().width,
splash->GetImageInfo().height, 0);
splash_img.emplace(instance, present_scheduler, info);
splash_img->flags &= ~VideoCore::GpuDirty;
texture_cache.RefreshImage(*splash_img);
splash_img->Transit(vk::ImageLayout::eTransferSrcOptimal,
vk::AccessFlagBits2::eTransferRead, {}, cmdbuf);
}
frame = GetRenderFrame();
}
const auto frame_subresources = vk::ImageSubresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
};
const auto pre_barrier =
vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eTransfer,
.srcAccessMask = vk::AccessFlagBits2::eTransferRead,
.dstStageMask = vk::PipelineStageFlagBits2::eTransfer,
.dstAccessMask = vk::AccessFlagBits2::eTransferWrite,
.oldLayout = vk::ImageLayout::eUndefined,
.newLayout = vk::ImageLayout::eTransferDstOptimal,
.image = frame->image,
.subresourceRange{frame_subresources}};
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.imageMemoryBarrierCount = 1,
.pImageMemoryBarriers = &pre_barrier,
});
cmdbuf.blitImage(splash_img->image, vk::ImageLayout::eTransferSrcOptimal, frame->image,
vk::ImageLayout::eTransferDstOptimal,
MakeImageBlitFit(splash->GetImageInfo().width, splash->GetImageInfo().height,
frame->width, frame->height),
vk::Filter::eLinear);
const auto post_barrier =
vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eTransfer,
.srcAccessMask = vk::AccessFlagBits2::eTransferWrite,
.dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput,
.dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite,
.oldLayout = vk::ImageLayout::eTransferDstOptimal,
.newLayout = vk::ImageLayout::eGeneral,
.image = frame->image,
.subresourceRange{frame_subresources}};
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.imageMemoryBarrierCount = 1,
.pImageMemoryBarriers = &post_barrier,
});
if (Config::getVkHostMarkersEnabled()) {
cmdbuf.endDebugUtilsLabelEXT();
}
// Flush frame creation commands.
frame->ready_semaphore = draw_scheduler.GetMasterSemaphore()->Handle();
frame->ready_tick = draw_scheduler.CurrentTick();
SubmitInfo info{};
draw_scheduler.Flush(info);
Present(frame);
return true;
}
Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) {
// Request a free presentation frame.
Frame* frame = GetRenderFrame();
if (frame->width != expected_frame_width || frame->height != expected_frame_height ||
frame->is_hdr != swapchain.GetHDR()) {
RecreateFrame(frame, expected_frame_width, expected_frame_height);
}
// EOP flips are triggered from GPU thread so use the drawing scheduler to record
// commands. Otherwise we are dealing with a CPU flip which could have arrived
// from any guest thread. Use a separate scheduler for that.
@ -420,6 +315,11 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
if (image_id != VideoCore::NULL_IMAGE_ID) {
auto& image = texture_cache.GetImage(image_id);
vk::Extent2D image_size = {image.info.size.width, image.info.size.height};
float ratio = (float)image_size.width / (float)image_size.height;
if (ratio != expected_ratio) {
expected_ratio = ratio;
}
image.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {},
cmdbuf);
@ -625,18 +525,43 @@ void Presenter::Present(Frame* frame, bool is_reusing_frame) {
ImGui::SetNextWindowDockID(dockId, ImGuiCond_Once);
ImGui::Begin("Display##game_display", nullptr, ImGuiWindowFlags_NoNav);
auto game_texture = frame->imgui_texture;
auto game_width = frame->width;
auto game_height = frame->height;
if (Libraries::SystemService::IsSplashVisible()) { // draw splash
if (!splash_img.has_value()) {
splash_img.emplace();
auto splash_path = Common::ElfInfo::Instance().GetSplashPath();
if (!splash_path.empty()) {
splash_img = ImGui::RefCountedTexture::DecodePngFile(splash_path);
}
}
if (auto& splash_image = this->splash_img.value()) {
auto [im_id, width, height] = splash_image.GetTexture();
game_texture = im_id;
game_width = width;
game_height = height;
}
}
ImVec2 contentArea = ImGui::GetContentRegionAvail();
const vk::Rect2D imgRect =
FitImage(frame->width, frame->height, (s32)contentArea.x, (s32)contentArea.y);
SetExpectedGameSize((s32)contentArea.x, (s32)contentArea.y);
ImGui::SetCursorPos(ImGui::GetCursorStartPos() + ImVec2{
(float)imgRect.offset.x,
(float)imgRect.offset.y,
});
ImGui::Image(frame->imgui_texture, {
static_cast<float>(imgRect.extent.width),
static_cast<float>(imgRect.extent.height),
});
const auto imgRect =
FitImage(game_width, game_height, (s32)contentArea.x, (s32)contentArea.y);
ImVec2 offset{
static_cast<float>(imgRect.offset.x),
static_cast<float>(imgRect.offset.y),
};
ImVec2 size{
static_cast<float>(imgRect.extent.width),
static_cast<float>(imgRect.extent.height),
};
ImGui::SetCursorPos(ImGui::GetCursorStartPos() + offset);
ImGui::Image(game_texture, size);
ImGui::End();
ImGui::PopStyleVar(3);
ImGui::PopStyleColor();
@ -707,19 +632,23 @@ Frame* Presenter::GetRenderFrame() {
}
}
if (frame->width != expected_frame_width || frame->height != expected_frame_height ||
frame->is_hdr != swapchain.GetHDR()) {
RecreateFrame(frame, expected_frame_width, expected_frame_height);
}
return frame;
}
void Presenter::SetExpectedGameSize(s32 width, s32 height) {
constexpr float expectedRatio = 1920.0 / 1080.0f;
const float ratio = (float)width / (float)height;
expected_frame_height = height;
expected_frame_width = width;
if (ratio > expectedRatio) {
expected_frame_width = static_cast<s32>(height * expectedRatio);
if (ratio > expected_ratio) {
expected_frame_width = static_cast<s32>(height * expected_ratio);
} else {
expected_frame_height = static_cast<s32>(width / expectedRatio);
expected_frame_height = static_cast<s32>(width / expected_ratio);
}
}

View file

@ -6,6 +6,7 @@
#include <condition_variable>
#include "imgui/imgui_config.h"
#include "imgui/imgui_texture.h"
#include "video_core/amdgpu/liverpool.h"
#include "video_core/renderer_vulkan/host_passes/fsr_pass.h"
#include "video_core/renderer_vulkan/host_passes/pp_pass.h"
@ -92,7 +93,6 @@ public:
}) != vo_buffers_addr.cend();
}
bool ShowSplash(Frame* frame = nullptr);
void Present(Frame* frame, bool is_reusing_frame = false);
void RecreateFrame(Frame* frame, u32 width, u32 height);
Frame* PrepareLastFrame();
@ -125,6 +125,7 @@ private:
void SetExpectedGameSize(s32 width, s32 height);
private:
float expected_ratio{1920.0 / 1080.0f};
u32 expected_frame_width{1920};
u32 expected_frame_height{1080};
@ -148,7 +149,7 @@ private:
std::mutex free_mutex;
std::condition_variable free_cv;
std::condition_variable_any frame_cv;
std::optional<VideoCore::Image> splash_img;
std::optional<ImGui::RefCountedTexture> splash_img;
std::vector<VAddr> vo_buffers_addr;
};