diff --git a/src/xenia/gpu/d3d12/d3d12_graphics_system.cc b/src/xenia/gpu/d3d12/d3d12_graphics_system.cc index 30b322f87..835c94c07 100644 --- a/src/xenia/gpu/d3d12/d3d12_graphics_system.cc +++ b/src/xenia/gpu/d3d12/d3d12_graphics_system.cc @@ -10,7 +10,9 @@ #include "xenia/gpu/d3d12/d3d12_graphics_system.h" #include "xenia/base/logging.h" +#include "xenia/base/math.h" #include "xenia/gpu/d3d12/d3d12_command_processor.h" +#include "xenia/gpu/draw_util.h" #include "xenia/ui/d3d12/d3d12_util.h" #include "xenia/xbox.h" @@ -265,22 +267,39 @@ void D3D12GraphicsSystem::Swap(xe::ui::UIEvent* e) { return; } + uint32_t window_width, window_height; + display_context_->GetSwapChainSize(window_width, window_height); + + int32_t target_x, target_y; + uint32_t target_width, target_height; + draw_util::GetPresentArea(swap_state.width, swap_state.height, window_width, + window_height, target_x, target_y, target_width, + target_height); + // For safety. + target_x = clamp(target_x, int32_t(D3D12_VIEWPORT_BOUNDS_MIN), + int32_t(D3D12_VIEWPORT_BOUNDS_MAX)); + target_y = clamp(target_y, int32_t(D3D12_VIEWPORT_BOUNDS_MIN), + int32_t(D3D12_VIEWPORT_BOUNDS_MAX)); + target_width = std::min( + target_width, uint32_t(int32_t(D3D12_VIEWPORT_BOUNDS_MAX) - target_x)); + target_height = std::min( + target_height, uint32_t(int32_t(D3D12_VIEWPORT_BOUNDS_MAX) - target_y)); + auto command_list = display_context_->GetSwapCommandList(); - uint32_t swap_width, swap_height; - display_context_->GetSwapChainSize(swap_width, swap_height); + // Assuming the window has already been cleared to the needed letterbox color. D3D12_VIEWPORT viewport; - viewport.TopLeftX = 0.0f; - viewport.TopLeftY = 0.0f; - viewport.Width = float(swap_width); - viewport.Height = float(swap_height); + viewport.TopLeftX = float(target_x); + viewport.TopLeftY = float(target_y); + viewport.Width = float(target_width); + viewport.Height = float(target_height); viewport.MinDepth = 0.0f; viewport.MaxDepth = 0.0f; command_list->RSSetViewports(1, &viewport); D3D12_RECT scissor; scissor.left = 0; scissor.top = 0; - scissor.right = swap_width; - scissor.bottom = swap_height; + scissor.right = window_width; + scissor.bottom = window_height; command_list->RSSetScissorRects(1, &scissor); command_list->SetDescriptorHeaps(1, &swap_srv_heap); StretchTextureToFrontBuffer( diff --git a/src/xenia/gpu/draw_util.cc b/src/xenia/gpu/draw_util.cc index 6c9ba1e73..d28df6d0e 100644 --- a/src/xenia/gpu/draw_util.cc +++ b/src/xenia/gpu/draw_util.cc @@ -9,6 +9,7 @@ #include "xenia/gpu/draw_util.h" +#include #include #include @@ -31,6 +32,36 @@ DEFINE_bool( "for certain games like GTA IV to work).", "GPU"); +DEFINE_bool( + present_stretch, true, + "Whether to rescale the image, instead of maintaining the original pixel " + "size, when presenting to the window. When this is disabled, other " + "positioning options are ignored.", + "GPU"); +DEFINE_bool( + present_letterbox, true, + "Maintain aspect ratio when stretching by displaying bars around the image " + "when there's no more overscan area to crop out.", + "GPU"); +// https://github.com/MonoGame/MonoGame/issues/4697#issuecomment-217779403 +// Using the value from DirectXTK (5% cropped out from each side, thus 90%), +// which is not exactly the Xbox One title-safe area, but close, and within the +// action-safe area: +// https://github.com/microsoft/DirectXTK/blob/1e80a465c6960b457ef9ab6716672c1443a45024/Src/SimpleMath.cpp#L144 +// XNA TitleSafeArea is 80%, but it's very conservative, designed for CRT, and +// is the title-safe area rather than the action-safe area. +// 90% is also exactly the fraction of 16:9 height in 16:10. +DEFINE_int32( + present_safe_area_x, 90, + "Percentage of the image width that can be kept when presenting to " + "maintain aspect ratio without letterboxing or stretching.", + "GPU"); +DEFINE_int32( + present_safe_area_y, 90, + "Percentage of the image height that can be kept when presenting to " + "maintain aspect ratio without letterboxing or stretching.", + "GPU"); + namespace xe { namespace gpu { namespace draw_util { @@ -589,6 +620,87 @@ ResolveCopyShaderIndex ResolveInfo::GetCopyShader( return shader; } +void GetPresentArea(uint32_t source_width, uint32_t source_height, + uint32_t window_width, uint32_t window_height, + int32_t& target_x_out, int32_t& target_y_out, + uint32_t& target_width_out, uint32_t& target_height_out) { + if (!cvars::present_stretch) { + target_x_out = (int32_t(window_width) - int32_t(source_width)) / 2; + target_y_out = (int32_t(window_height) - int32_t(source_height)) / 2; + target_width_out = source_width; + target_height_out = source_height; + return; + } + // Prevent division by zero. + if (!source_width || !source_height) { + target_x_out = 0; + target_y_out = 0; + target_width_out = 0; + target_height_out = 0; + return; + } + if (uint64_t(window_width) * source_height > + uint64_t(source_width) * window_height) { + // The window is wider that the source - crop along Y, then letterbox or + // stretch along X. + uint32_t present_safe_area; + if (cvars::present_safe_area_y > 0 && cvars::present_safe_area_y < 100) { + present_safe_area = uint32_t(cvars::present_safe_area_y); + } else { + present_safe_area = 100; + } + uint32_t target_height = + uint32_t(uint64_t(window_width) * source_height / source_width); + bool letterbox = false; + if (target_height * present_safe_area > window_height * 100) { + // Don't crop out more than the safe area margin - letterbox or stretch. + target_height = window_height * 100 / present_safe_area; + letterbox = true; + } + if (letterbox && cvars::present_letterbox) { + uint32_t target_width = + uint32_t(uint64_t(source_width) * window_height * 100 / + (source_height * present_safe_area)); + target_x_out = (int32_t(window_width) - int32_t(target_width)) / 2; + target_width_out = target_width; + } else { + target_x_out = 0; + target_width_out = window_width; + } + target_y_out = (int32_t(window_height) - int32_t(target_height)) / 2; + target_height_out = target_height; + } else { + // The window is taller than the source - crop along X, then letterbox or + // stretch along Y. + uint32_t present_safe_area; + if (cvars::present_safe_area_x > 0 && cvars::present_safe_area_x < 100) { + present_safe_area = uint32_t(cvars::present_safe_area_x); + } else { + present_safe_area = 100; + } + uint32_t target_width = + uint32_t(uint64_t(window_height) * source_width / source_height); + bool letterbox = false; + if (target_width * present_safe_area > window_width * 100) { + // Don't crop out more than the safe area margin - letterbox or stretch. + target_width = window_width * 100 / present_safe_area; + letterbox = true; + } + if (letterbox && cvars::present_letterbox) { + uint32_t target_height = + uint32_t(uint64_t(source_height) * window_width * 100 / + (source_width * present_safe_area)); + target_y_out = (int32_t(window_height) - int32_t(target_height)) / 2; + target_height_out = target_height; + } else { + target_y_out = 0; + target_height_out = window_height; + } + target_x_out = (int32_t(window_width) - int32_t(target_width)) / 2; + target_width_out = target_width; + } +} + } // namespace draw_util } // namespace gpu } // namespace xe diff --git a/src/xenia/gpu/draw_util.h b/src/xenia/gpu/draw_util.h index 76827c093..edb880ab0 100644 --- a/src/xenia/gpu/draw_util.h +++ b/src/xenia/gpu/draw_util.h @@ -272,6 +272,14 @@ bool GetResolveInfo(const RegisterFile& regs, const Memory& memory, TraceWriter& trace_writer, uint32_t resolution_scale, bool edram_16_as_minus_1_to_1, ResolveInfo& info_out); +// Taking user configuration - stretching or letterboxing, overscan region to +// crop to fill while maintaining the aspect ratio - into account, returns the +// area where the frame should be presented in the host window. +void GetPresentArea(uint32_t source_width, uint32_t source_height, + uint32_t window_width, uint32_t window_height, + int32_t& target_x_out, int32_t& target_y_out, + uint32_t& target_width_out, uint32_t& target_height_out); + } // namespace draw_util } // namespace gpu } // namespace xe diff --git a/src/xenia/ui/d3d12/d3d12_context.cc b/src/xenia/ui/d3d12/d3d12_context.cc index 506ca6141..f897a5516 100644 --- a/src/xenia/ui/d3d12/d3d12_context.cc +++ b/src/xenia/ui/d3d12/d3d12_context.cc @@ -300,9 +300,9 @@ void D3D12Context::BeginSwap() { clear_color[1] = 1.0f; clear_color[2] = 0.0f; } else { - clear_color[0] = 238.0f / 255.0f; - clear_color[1] = 238.0f / 255.0f; - clear_color[2] = 238.0f / 255.0f; + clear_color[0] = 0.0f; + clear_color[1] = 0.0f; + clear_color[2] = 0.0f; } clear_color[3] = 1.0f; swap_command_list_->ClearRenderTargetView(back_buffer_rtv, clear_color, 0, diff --git a/src/xenia/ui/window_win.cc b/src/xenia/ui/window_win.cc index 2e60d2e42..c86a5cec8 100644 --- a/src/xenia/ui/window_win.cc +++ b/src/xenia/ui/window_win.cc @@ -253,20 +253,6 @@ bool Win32Window::ReleaseMouse() { bool Win32Window::is_fullscreen() const { return fullscreen_; } -// https://blogs.msdn.microsoft.com/oldnewthing/20131017-00/?p=2903 -BOOL UnadjustWindowRect(LPRECT prc, DWORD dwStyle, BOOL fMenu) { - RECT rc; - SetRectEmpty(&rc); - BOOL fRc = AdjustWindowRect(&rc, dwStyle, fMenu); - if (fRc) { - prc->left -= rc.left; - prc->top -= rc.top; - prc->right -= rc.right; - prc->bottom -= rc.bottom; - } - return fRc; -} - void Win32Window::ToggleFullscreen(bool fullscreen) { if (fullscreen == is_fullscreen()) { return; @@ -288,9 +274,6 @@ void Win32Window::ToggleFullscreen(bool fullscreen) { AdjustWindowRect(&rc, GetWindowLong(hwnd_, GWL_STYLE), false); MoveWindow(hwnd_, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE); - - width_ = rc.right - rc.left; - height_ = rc.bottom - rc.top; } } else { // Reinstate borders, resize to 1280x720 @@ -301,15 +284,13 @@ void Win32Window::ToggleFullscreen(bool fullscreen) { if (main_menu) { ::SetMenu(hwnd_, main_menu->handle()); } - - auto& rc = windowed_pos_.rcNormalPosition; - bool has_menu = main_menu_ ? true : false; - UnadjustWindowRect(&rc, GetWindowLong(hwnd_, GWL_STYLE), has_menu); - width_ = rc.right - rc.left; - height_ = rc.bottom - rc.top; } fullscreen_ = fullscreen; + + // width_ and height_ will be updated by the WM_SIZE handler - + // windowed_pos_.rcNormalPosition is also not the correct source for them when + // switching from fullscreen to maximized. } bool Win32Window::is_bordered() const {