diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 69ae89120..d44f0890d 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -161,11 +161,12 @@ const std::string kRecentlyPlayedTitlesFilename = "recent.toml"; const std::string kBaseTitle = "Xenia-canary"; EmulatorWindow::EmulatorWindow(Emulator* emulator, - ui::WindowedAppContext& app_context) + ui::WindowedAppContext& app_context, + uint32_t width, uint32_t height) : emulator_(emulator), app_context_(app_context), window_listener_(*this), - window_(ui::Window::Create(app_context, kBaseTitle, 1280, 720)), + window_(ui::Window::Create(app_context, kBaseTitle, width, height)), imgui_drawer_( std::make_unique(window_.get(), kZOrderImGui)), display_config_game_config_load_callback_( @@ -190,10 +191,11 @@ EmulatorWindow::EmulatorWindow(Emulator* emulator, } std::unique_ptr EmulatorWindow::Create( - Emulator* emulator, ui::WindowedAppContext& app_context) { + Emulator* emulator, ui::WindowedAppContext& app_context, uint32_t width, + uint32_t height) { assert_true(app_context.IsInUIThread()); std::unique_ptr emulator_window( - new EmulatorWindow(emulator, app_context)); + new EmulatorWindow(emulator, app_context, width, height)); if (!emulator_window->Initialize()) { return nullptr; } diff --git a/src/xenia/app/emulator_window.h b/src/xenia/app/emulator_window.h index 1a0dc9564..79c8aea3a 100644 --- a/src/xenia/app/emulator_window.h +++ b/src/xenia/app/emulator_window.h @@ -54,7 +54,8 @@ class EmulatorWindow { virtual ~EmulatorWindow(); static std::unique_ptr Create( - Emulator* emulator, ui::WindowedAppContext& app_context); + Emulator* emulator, ui::WindowedAppContext& app_context, uint32_t width, + uint32_t height); std::unique_ptr Gamepad_HotKeys_Listener; @@ -183,7 +184,8 @@ class EmulatorWindow { }; explicit EmulatorWindow(Emulator* emulator, - ui::WindowedAppContext& app_context); + ui::WindowedAppContext& app_context, uint32_t width, + uint32_t height); bool Initialize(); diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc index 15c6edd62..9dac67c0d 100644 --- a/src/xenia/app/xenia_main.cc +++ b/src/xenia/app/xenia_main.cc @@ -103,6 +103,8 @@ DECLARE_bool(debug); DEFINE_bool(discord, true, "Enable Discord rich presence", "General"); +DECLARE_bool(widescreen); + namespace xe { namespace app { @@ -445,8 +447,17 @@ bool EmulatorApp::OnInitialize() { emulator_ = std::make_unique("", storage_root, content_root, cache_root); + // Determine window size based on user widescreen setting. + uint32_t window_w = 1280; + uint32_t window_h = 720; + if (!cvars::widescreen) { + window_w = 1024; + window_h = 768; + } + // Main emulator display window. - emulator_window_ = EmulatorWindow::Create(emulator_.get(), app_context()); + emulator_window_ = EmulatorWindow::Create(emulator_.get(), app_context(), + window_w, window_h); if (!emulator_window_) { XELOGE("Failed to create the main emulator window"); return false; diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.cc b/src/xenia/gpu/d3d12/d3d12_command_processor.cc index 5c2787bf3..430b7a9d6 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.cc +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.cc @@ -2205,9 +2205,11 @@ void D3D12CommandProcessor::IssueSwap(uint32_t frontbuffer_ptr, } D3D12_RESOURCE_DESC swap_texture_desc = swap_texture_resource->GetDesc(); + auto aspect = graphics_system_->GetScaledAspectRatio(); + presenter->RefreshGuestOutput( uint32_t(swap_texture_desc.Width), uint32_t(swap_texture_desc.Height), - 1280, 720, + aspect.first, aspect.second, [this, &swap_texture_srv_desc, frontbuffer_format, swap_texture_resource, &swap_texture_desc]( ui::Presenter::GuestOutputRefreshContext& context) -> bool { diff --git a/src/xenia/gpu/graphics_system.cc b/src/xenia/gpu/graphics_system.cc index 0f1ec6f8d..85d5ee943 100644 --- a/src/xenia/gpu/graphics_system.cc +++ b/src/xenia/gpu/graphics_system.cc @@ -67,6 +67,9 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor, kernel_state_ = kernel_state; app_context_ = app_context; + scaled_aspect_x_ = 16; + scaled_aspect_y_ = 9; + if (provider_) { // Safe if either the UI thread call or the presenter creation fails. if (app_context_) { diff --git a/src/xenia/gpu/graphics_system.h b/src/xenia/gpu/graphics_system.h index aa3662edc..6044bfd3c 100644 --- a/src/xenia/gpu/graphics_system.h +++ b/src/xenia/gpu/graphics_system.h @@ -86,6 +86,14 @@ class GraphicsSystem { bool Save(ByteStream* stream); bool Restore(ByteStream* stream); + std::pair GetScaledAspectRatio() const { + return {scaled_aspect_x_, scaled_aspect_y_}; + }; + void SetScaledAspectRatio(uint32_t x, uint32_t y) { + scaled_aspect_x_ = x; + scaled_aspect_y_ = y; + }; + protected: GraphicsSystem(); @@ -117,6 +125,9 @@ class GraphicsSystem { bool paused_ = false; + uint32_t scaled_aspect_x_ = 0; + uint32_t scaled_aspect_y_ = 0; + private: std::unique_ptr presenter_; diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index 317dd1cb7..923a95e39 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -1286,8 +1286,11 @@ void VulkanCommandProcessor::IssueSwap(uint32_t frontbuffer_ptr, return; } + auto aspect = graphics_system_->GetScaledAspectRatio(); + presenter->RefreshGuestOutput( - frontbuffer_width_scaled, frontbuffer_height_scaled, 1280, 720, + frontbuffer_width_scaled, frontbuffer_height_scaled, aspect.first, + aspect.second, [this, frontbuffer_width_scaled, frontbuffer_height_scaled, frontbuffer_format, swap_texture_view]( ui::Presenter::GuestOutputRefreshContext& context) -> bool { diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc index e11b516bf..dd0adaa49 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc @@ -49,6 +49,12 @@ DEFINE_int32( "Video"); DEFINE_bool(use_50Hz_mode, false, "Enables usage of PAL-50 mode.", "Video"); +DEFINE_bool(interlaced, false, "Toggles interlaced mode.", "Video"); + +// TODO: This is stored in XConfig somewhere, probably in video flags. +DEFINE_bool(widescreen, true, "Toggles between 16:9 and 4:3 aspect ratio.", + "Video"); + // BT.709 on modern monitors and TVs looks the closest to the Xbox 360 connected // to an HDTV. DEFINE_uint32(kernel_display_gamma_type, 2, @@ -88,6 +94,51 @@ inline constexpr static float GetVideoRefreshRate() { return cvars::use_50Hz_mode ? 50.0f : 60.0f; } +inline constexpr static std::pair GetDisplayAspectRatio() { + if (cvars::widescreen) { + return {16, 9}; + } + + return {4, 3}; +} + +static std::pair CalculateScaledAspectRatio(uint32_t fb_x, + uint32_t fb_y) { + // Calculate the game's final aspect ratio as it would appear on a physical + // TV. + auto dar = GetDisplayAspectRatio(); + uint32_t display_x = dar.first; + uint32_t display_y = dar.second; + + auto res = GetInternalDisplayResolution(); + uint32_t res_x = res.first; + uint32_t res_y = res.second; + + uint32_t x_factor = std::gcd(fb_x, res_x); + res_x /= x_factor; + fb_x /= x_factor; + uint32_t y_factor = std::gcd(fb_y, res_y); + res_y /= y_factor; + fb_y /= y_factor; + + display_x = display_x * res_x - display_x * (res_x - fb_x); + display_y *= res_x; + + display_y = display_y * res_y - display_y * (res_y - fb_y); + display_x *= res_y; + + uint32_t aspect_factor = std::gcd(display_x, display_y); + display_x /= aspect_factor; + display_y /= aspect_factor; + + XELOGI( + "Hardware scaler: width ratio {}:{}, height ratio {}:{}, final aspect " + "ratio {}:{}", + fb_x, res_x, fb_y, res_y, display_x, display_y); + + return {display_x, display_y}; +} + namespace xe { namespace kernel { namespace xboxkrnl { @@ -184,6 +235,7 @@ void VdGetCurrentDisplayInformation_entry( display_info->display_width = (uint16_t)mode.display_width; display_info->display_height = (uint16_t)mode.display_height; display_info->display_refresh_rate = mode.refresh_rate; + display_info->display_interlaced = mode.is_interlaced; display_info->actual_display_width = (uint16_t)mode.display_width; } DECLARE_XBOXKRNL_EXPORT1(VdGetCurrentDisplayInformation, kVideo, kStub); @@ -196,9 +248,8 @@ void VdQueryVideoMode(X_VIDEO_MODE* video_mode) { video_mode->display_width = display_res.first; video_mode->display_height = display_res.second; - video_mode->is_interlaced = 0; - video_mode->is_widescreen = - ((video_mode->display_width / 4) > (video_mode->display_height / 3)); + video_mode->is_interlaced = cvars::interlaced; + video_mode->is_widescreen = cvars::widescreen; video_mode->is_hi_def = video_mode->display_width >= 0x400; video_mode->refresh_rate = GetVideoRefreshRate(); video_mode->video_standard = GetVideoStandard(); @@ -350,6 +401,14 @@ dword_result_t VdInitializeScalerCommandBuffer_entry( for (size_t i = 0; i < dest_count; ++i) { dest[i] = 0x80000000; } + + uint32_t fb_x = (scaled_output_wh >> 16) & 0xFFFF; + uint32_t fb_y = scaled_output_wh & 0xFFFF; + auto aspect = CalculateScaledAspectRatio(fb_x, fb_y); + + auto graphics_system = kernel_state()->emulator()->graphics_system(); + graphics_system->SetScaledAspectRatio(aspect.first, aspect.second); + return (uint32_t)dest_count; } DECLARE_XBOXKRNL_EXPORT2(VdInitializeScalerCommandBuffer, kVideo, kImplemented,